Coder Social home page Coder Social logo

mental32 / spotify.py Goto Github PK

View Code? Open in Web Editor NEW
149.0 11.0 38.0 543 KB

๐ŸŒ API wrapper for Spotify ๐ŸŽถ

Home Page: https://spotifypy.readthedocs.io/en/latest/

License: MIT License

Python 99.70% Makefile 0.24% Shell 0.06%
api-wrapper spotify-api spotify python3 spotify-library spotify-sdk asynchronous async synchronous

spotify.py's Introduction

logo

Version infoGithub IssuesGithub forksGitHub starsLicenseTravis


spotify.py

An API library for the spotify client and the Spotify Web API written in Python.

Spotify.py is an asynchronous API library for Spotify. While maintaining an emphasis on being purely asynchronous the library provides syncronous functionality with the spotify.sync module.

import spotify.sync as spotify  # Nothing requires async/await now!

Notice: Looking For Maintainers

The author of spotify.py considers it deprecated and is provided "as is".

(of course depending on the user it may be considered feature complete)

The author does not intend to continue working on it or providing support, it may work or may not for the purposes it was designed for.

If you encounter an issue:

  • open an issue and wait for a PR to come along and fix (you may be waiting a while)
  • open a PR that introduces the fix directly (the author is happy to click a button labeled "merge")

Index

Installing

To install the library simply clone it and run pip.

  • git clone https://github.com/mental32/spotify.py spotify_py
  • cd spotify_py
  • pip3 install -U .

or use pypi

  • pip3 install -U spotify (latest stable)
  • pip3 install -U git+https://github.com/mental32/spotify.py#egg=spotify (nightly)

Examples

Sorting a playlist by popularity

import sys
import getpass

import spotify

async def main():
    playlist_uri = input("playlist_uri: ")
    client_id = input("client_id: ")
    secret = getpass.getpass("application secret: ")
    token = getpass.getpass("user token: ")

    async with spotify.Client(client_id, secret) as client:
        user = await spotify.User.from_token(client, token)

        async for playlist in user:
            if playlist.uri == playlist_uri:
                return await playlist.sort(reverse=True, key=(lambda track: track.popularity))

        print('No playlists were found!', file=sys.stderr)

if __name__ == '__main__':
    client.loop.run_until_complete(main())

Required oauth scopes for methods

import spotify
from spotify.oauth import get_required_scopes

# In order to call this method sucessfully the "user-modify-playback-state" scope is required.
print(get_required_scopes(spotify.Player.play))  # => ["user-modify-playback-state"]

# Some methods have no oauth scope requirements, so `None` will be returned instead.
print(get_required_scopes(spotify.Playlist.get_tracks))  # => None

Usage with flask

import string
import random
from typing import Tuple, Dict

import flask
import spotify.sync as spotify

SPOTIFY_CLIENT = spotify.Client('SPOTIFY_CLIENT_ID', 'SPOTIFY_CLIENT_SECRET')

APP = flask.Flask(__name__)
APP.config.from_mapping({'spotify_client': SPOTIFY_CLIENT})

REDIRECT_URI: str = 'http://localhost:8888/spotify/callback'

OAUTH2_SCOPES: Tuple[str] = ('user-modify-playback-state', 'user-read-currently-playing', 'user-read-playback-state')
OAUTH2: spotify.OAuth2 = spotify.OAuth2(SPOTIFY_CLIENT.id, REDIRECT_URI, scopes=OAUTH2_SCOPES)

SPOTIFY_USERS: Dict[str, spotify.User] = {}


@APP.route('/spotify/callback')
def spotify_callback():
    try:
        code = flask.request.args['code']
    except KeyError:
        return flask.redirect('/spotify/failed')
    else:
        key = ''.join(random.choice(string.ascii_uppercase) for _ in range(16))
        SPOTIFY_USERS[key] = spotify.User.from_code(
            SPOTIFY_CLIENT,
            code,
            redirect_uri=REDIRECT_URI
        )

        flask.session['spotify_user_id'] = key

    return flask.redirect('/')

@APP.route('/spotify/failed')
def spotify_failed():
    flask.session.pop('spotify_user_id', None)
    return 'Failed to authenticate with Spotify.'

@APP.route('/')
@APP.route('/index')
def index():
    try:
        return repr(SPOTIFY_USERS[flask.session['spotify_user_id']])
    except KeyError:
        return flask.redirect(OAUTH2.url)

if __name__ == '__main__':
    APP.run('127.0.0.1', port=8888, debug=False)

Resources

For resources look at the examples

spotify.py's People

Contributors

ajp2455 avatar appsworld avatar bmeares avatar clement-ldr avatar frafra avatar gschaffner avatar igorminotto avatar javascriptdude avatar m3nix avatar mental32 avatar micahdlamb avatar mikonse avatar owlwang avatar phlmn avatar thunderson avatar vveeps avatar winston-yallow avatar ycd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spotify.py's Issues

Impossible to play list of tracks

This adresses a few issues with the current method to play tracks/contexts. It works when given a context but fails when given tracks.

When trying to play a list of tracks with player.play(track01, track02, device=device_id) the error TypeError: list() takes at most 1 argument (2 given) is raised. This happens in the HTTPClient method play_playback(). The list constructor should be used with only one argument, the iterable.

In the same method: The initialisation of can_set_context tries to access payload["context_uri"] even if this does not exist.

When trying to play a single track with player.play(track, device=device_id) the track will be interpreted as a context. It is not possible to play a single track. (This is especially weird when passing the result of a track search into the play function: It fails only for searches with only one search result and works for other results.)

An optional argument treat_as_tracks would be nice: If set to true it will interpret all *uris as track uris. This way it is possible to pass only one track in cases where you know that it will be a track. This is also backwards compatible as the new argument is optional, the default is the old behaviour.

I have fixed all of the above in my fork and will create a PR, however I am not sure if my fixes are the best way to do this.

Problem with OAuth scopes

Hi!

I'm currently testing the Flask example for authenticating (I applied these two small changes, but everything else is a fresh clone from master, v 0.6.0). I'm getting an error from the OAuth module when trying to set up the scopes:

Traceback (most recent call last):
  File "spotify_test3.py", line 11, in <module>
    OAUTH2: spotify.OAuth2 = spotify.OAuth2(SPOTIFY_CLIENT.id, REDIRECT_URI, scopes=['user-modify-playback-state','user-read-currently-playing','user-read-playback-state'])
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/oauth.py", line 105, in __init__
    self.set_scopes(**scopes)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/oauth.py", line 178, in set_scopes
    method = self.scopes.add if bool(state) else self.scopes.remove
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/oauth.py", line 135, in scopes
    return MappingProxyType(self.__scopes)
TypeError: mappingproxy() argument must be a mapping, not set
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f1c7332d190>

In my case the scopes property is called with self.__scopes as a set (I don't see that attributed changing anywhere), but even if I call it with a dictionary, the MappingProxyType object will not have an .add (or .remove) method anyway. So I'm a bit lost

FYI: many things you're doing in your code are pretty new to me, so apologies for the dumb questions

When using album.get_all_tracks(), an error is produced. `'Nonetype' has no attribute 'images'`

Error is as the title says. Heres here traceback.

AttributeError: 'NoneType' object has no attribute 'images'
Traceback (most recent call last):
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\discord\ext\commands\core.py", line 83, in wrapped
    ret = await coro(*args, **kwargs)
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\Life\cogs\voice\music.py", line 70, in play
    search_result = await ctx.player.get_results(ctx=ctx, search=search)
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\Life\cogs\voice\utilities\player.py", line 97, in get_results
    spotify_tracks = await result.get_all_tracks()
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\album.py", line 125, in get_all_tracks
    return [track async for track in self]
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\album.py", line 125, in <listcomp>
    return [track async for track in self]
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\base.py", line 145, in __aiter__
    yield klass(client, item)  # pylint: disable=not-callable
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\track.py", line 78, in __init__
    self.images = album.images.copy()

I think this may be an api change as only a week ago with the same album this function worked normally, Now it is broken.

Playlist attribute `tracks` is always None

I'm new to this so forgive me in advance

I'm following the Flask example, and whenever I try to access the tracks attribute of Playlist, it gives me None. I'm also not able to use the remove() method of Playlist, and it throws me ValueError: list.remove(x): x not in list since tracks is None

def start():
    currentUser: spotify.User = SPOTIFY_USERS[flask.session['spotify_user_id']]
    if currentUser.id in listeningSessions:
        return flask.redirect(flask.url_for('.queue'))
    try:
        currentUser.currently_playing()['item']
    except KeyError:
        flask.flash("Playback device not found.. Please open your Spotify app and begin playing (any random song)",
                    category='error')
        return flask.redirect(flask.url_for('.index'))  # todo handle error
    playlists = currentUser.get_playlists()
    playlist: spotify.Playlist = None
    for p in playlists:
        print(p.name, p.name == 'Spo2fi Queue')  # Spo2fi Queue True
        if p.name == 'Spo2fi Queue':
            playlist = p
            break

    print(playlist.tracks)  # None
    if not playlist:
        playlist = currentUser.create_playlist('Spo2fi Queue', collaborative=True, public=False)

    print(playlist.tracks)  # None

    try:
        if currentUser.currently_playing()['is_playing']:
            playlist.replace_tracks(currentUser.currently_playing()['item'])
    except KeyError:
        usersTopTracks = currentUser.top_tracks(limit=1, time_range='medium_term')
        print(usersTopTracks)
        playlist.replace_tracks(usersTopTracks[0])
    print(playlist.tracks)  # None

    joinId = ''.join(random.choice(string.ascii_uppercase + string.digits) for i in range(4))
    while joinId in parties:
        joinId = ''.join(random.choice(string.ascii_uppercase + string.digits) for i in range(4))
    listeningSessions[currentUser.id] = ListeningSession(currentUser, [], playlist, joinId)
    parties[joinId] = currentUser.id
    print(playlist.tracks)  # None

    print(currentUser.currently_playing())  # works
    flask.session['party'] = listeningSessions[currentUser.id].joinId
    print(flask.session['party'])  # works

    try:
        currentUser.get_player().play(playlist)
        currentUser.get_player().shuffle(False)
    except spotify.errors.NotFound:
        flask.flash("Playback device not found.. Please open your Spotify app and begin playing (any random song)",
                    category='error')
        return flask.redirect(flask.url_for('.index'))  # todo handle error
    print(playlist.tracks)  # None
    return flask.redirect(flask.url_for('.queue'))

How to do get genre_tag of a song/track ?

Hi. I am Priyansh. I just now installed the library and wanted to extract genre for a given song/track name as input (search query). I cannot see anywhere the functionality to get genre tags for a track.
We can do the same for album but not for track. Is your implementation complete or spotify doesn't provide the genre info. of track ? Meanwhile I find a bug in the api docs. The genres attribute should changed to genre for the album class else it will give the error

No search results for "Adventure Club"

Hi @mental32 -- thanks for the great module.

Stumbled upon this by accident:

1. Search for "Adventure Club" via search method yields empty list:

client.search("Adventure Club", types=["artist"])
{'artists': []}

2. Whereas search for "Adventure" yields "Adventure Club" in top spot:

client.search("Adventure", types=["artist"])

{'artists': [<spotify.Artist: "Adventure Club">,
  <spotify.Artist: "Adventure Time">,
  <spotify.Artist: "A Space Love Adventure">,
  <spotify.Artist: "Leaf Adventure">,
  <spotify.Artist: "Sonic Adventure Music Experience">,
  <spotify.Artist: "Cast - Olaf's Frozen Adventure">,
  <spotify.Artist: "Adventure Galley">,
  <spotify.Artist: "Action/Adventure">,
  <spotify.Artist: "Adventure Tiger">,
  <spotify.Artist: "A Great Adventure or Nothing">,
  <spotify.Artist: "Adventure Violence">,
  <spotify.Artist: "Sonic Adventure Project">,
  <spotify.Artist: "Techno Kitten Adventure">,
  <spotify.Artist: "Grand Space Adventure">,
  <spotify.Artist: "Adventure">,
  <spotify.Artist: "Inner Adventure">,
  <spotify.Artist: "Adventure Time">,
  <spotify.Artist: "New Street Adventure">,
  <spotify.Artist: "Mr. Bacon's Big Adventure">,
  <spotify.Artist: "Hand-Me-Down Adventure">]}

3. And: Using Spotify's API sandbox to issue the query in 1 also yields the correct results:

https://developer.spotify.com/console/get-search-item/?q=Adventure%20Club&type=artist&market=&limit=&offset=

yields:

{
  "artists": {
    "href": "https://api.spotify.com/v1/search?query=Adventure+Club&type=artist&market=CH&offset=0&limit=20",
    "items": [
      {
        "external_urls": {
          "spotify": "https://open.spotify.com/artist/5CdJjUi9f0cVgo9nFuJrFa"
        },
        "followers": {
          "href": null,
          "total": 228604
        },
        "genres": [
          "big room",
          "brostep",
          "canadian electronic",
          "chillstep",
          "complextro",
          "edm",
          "electro house",
          "electronic trap",
          "filthstep",
          "pop edm"
        ],
        "href": "https://api.spotify.com/v1/artists/5CdJjUi9f0cVgo9nFuJrFa",
        "id": "5CdJjUi9f0cVgo9nFuJrFa",
        "images": [
          {
            "height": 640,
            "url": "https://i.scdn.co/image/3b0069fe11e50bba1b737a3189f10a78ef3bf8ce",
            "width": 640
          },
          {
            "height": 320,
            "url": "https://i.scdn.co/image/866b5d49f54160f76d3da80d790b5c5f420996af",
            "width": 320
          },
          {
            "height": 160,
            "url": "https://i.scdn.co/image/c3299135895611be2fdb223d844b6e720cd8dd44",
            "width": 160
          }
        ],
        "name": "Adventure Club",
        "popularity": 61,
        "type": "artist",
        "uri": "spotify:artist:5CdJjUi9f0cVgo9nFuJrFa"
      },
      {
        "external_urls": {
          "spotify": "https://open.spotify.com/artist/4vIDWbhxIjNMXQ18tfmLxS"
        },
        "followers": {
          "href": null,
          "total": 1071
        },
        "genres": [],
        "href": "https://api.spotify.com/v1/artists/4vIDWbhxIjNMXQ18tfmLxS",
        "id": "4vIDWbhxIjNMXQ18tfmLxS",
        "images": [
          {
            "height": 640,
            "url": 
[...]

Do you have an idea what is going on here?

Thanks!

Working With VPN

It seems it doesn't work with VPN or Third Party Proxy Applications
Can You Add Proxy Support to it?

Unclosed client session client_session: <aiohttp.client.ClientSession object at 0x000001E6F763E400> Unclosed connector connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000001E6F7769F48>, 806136.265)]', '[(<aiohttp.client_proto.ResponseHandler object at 0x000001E6F77A5228>, 806138.421)]'] connector: <aiohttp.connector.TCPConnector object at 0x000001E6F763E438>

On Searching Example with using VPN .

Feature Idea - Pass limit=None to get all playlists, tracks, ...

When I call user.get_playlists() and user.library.get_tracks() I need all the playlists/tracks but it currently limits me to getting 50 at a time.

It would be nice if I could pass limit=None to get them all at once. I'll make a pull request if you think its worth doing.

OAuth needs "scopes" oauth2_url use "scope"

  File "/home/frafra/.cache/pypoetry/virtualenvs/spotify-share-library-sjzLS3P8-py3.7/lib64/python3.7/site-packages/spotify/client.py", line 140, in oauth2_url
    state=state,
  File "/home/frafra/.cache/pypoetry/virtualenvs/spotify-share-library-sjzLS3P8-py3.7/lib64/python3.7/site-packages/spotify/oauth.py", line 147, in url_only
    oauth = OAuth2(**kwargs)
TypeError: __init__() got an unexpected keyword argument 'scope'

scope : Optional[:class:`str`]
Space seperated spotify scopes for different levels of access.

Improvements for authentication flow

I would like to propose some improvements to the API for initializing Spotify Users.
Currently, as of my pull request from a few days ago there are multiple ways to create an authenticated User object:
User.from_code, User.from_token, and User.from_refresh_token.

There is two improvements I'd like to make. First combine the from_token and from_refresh_token methods into one, e.g. from_token_info that takes a tuple of optional parameters, access_token, expiry_time, refresh_token and then correctly instantiates and authenticates the User depending on what tokens it got.

The second improvement is the option to add a cache path to store the access and refresh tokens and later on reinitialize the User by just providing the cache path. This would make it easier to have persistent applications where one does not want to go through the oauth flow every time and does not want to bother with storing the tokens themselves.

I have the changes ready and will make a pull request soon, just wanted to open this issue to allow for discussion about the changes.

Token refreshing issue

I am doing something similar to your "Usage with flask" example where I store User objects in a dict for an indefinite period of time. I've been using the refresh=True option to automatically refresh the token (which works) but it doesn't seem good to me that it is continuously refreshing the token forever when I'm not making any requests with the user. This is especially bad if I create multiple copies of the User object which all keep refreshing. Ultimately, I don't think I should be storing Users in a dict forever but that's another discussion which I believe mikonse's pull request is trying to address.

My thought is that it would make more sense if you handled the token expiration in HttpClient.request by catching the exception:
spotify.errors.HTTPException: Unauthorized (status code: 401): The access token expired
refreshing and retrying the request (just like you are doing for several other exceptions). I saw it done this way in some other oauth implementations too.

When I looked at the HttpClient.request code I was surprised to see you are actually handling 401 and calling self.get_bearer_info(). However, I'm not sure what that function is supposed to be doing... It doesn't take the code, token or refresh_token as input. I'm pretty sure its not working because the 401 exception just keeps happening RETRY_AMOUNT times before raising out of the loop.

Makefile install fails

The make install command still uses the setup.py file which was replaced by the pyproject.toml. I do know that the correct way to install this is with pip3 install -U .

(This is not really important, I just find it weird that the makefile references an obsolete file.)

Struggling to get user token

Hi Mental32, I've been trying to access my personal spotify data using your top_tracks example. I have a client set up and I understand that I need some user token (user = await client.user_from_token("sometoken"). How can I obtain this token easily, without building some ui?

Thanks for your time

Issue with session closing

Version: master
Python: 3.7.5
aiohttp (3.6.2)

I tried both modes, async and sync, same result :

2019-12-07 10:39:21 asyncio[3439466] ERROR Unclosed client session client_session: <aiohttp.client.ClientSession object at 0x7fe47f168810> 2019-12-07 10:39:21 asyncio[3439466] ERROR Unclosed connector connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7fe47f1514b0>, 1495824.926715675)]'] connector: <aiohttp.connector.TCPConnector object at 0x7fe47f168850>

It seems I have to manually close it : await user.http.close()

Moreover, in your README, there is :
client.loop.run_until_complete(main())

Should it be "asyncio.run(main())" ?

Rate limiting? Caching?

Does your module support rate limiting? If so, how to activate it? What about caching? Are there any 3rd party modules that you can recommend integrating for that purpose?

Better http throttling post 429s

When i do something like:

for song in music_files:
        tasks.append(asyncio.create_task(client.search(
            cleanup_name(song.name),
            types=["track"])))
  done = await asyncio.gather(*tasks)

And i get ratelimited, the client, instead of waiting, justs tries requesting and exits with error code 439(Too many requests), when it should wait for the ratelimit to pass.

Spotify Register Problem

Hi, @mental32 ! thanks for the fantastic work, your library is beautiful, I am using it to the fullest extent in my project)
However, there is a problem in registering the Spotify client
Calling the following code:

spotify.User.from_code (
ย ย ย ย ย ย ย ย ย ย ย ย  code = code,
ย ย ย ย ย ย ย ย ย ย ย ย  client = spotify_cl,
ย ย ย ย ย ย ย ย ย ย ย ย  redirect_uri = self.redirect_uri,
)
I came across the following problem in the init method of the User class: /spotify/models/user.py, 88 line of code
self.url = data.poprecently ("external_urls"). get ("spotify", None)
-> AttributeError: 'dict' object has no attribute 'poprecently'

If you comment out this line, then everything is in order and everything works, please correct this moment.
Sincerely, Roman Demyanchuk!

Cannot import

Hi!

So, I'm trying to use this library, byt apparentely I cannot use it in python3.5 as i cannot import it. The error is:

Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> import spotify
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.5/dist-packages/spotify/__init__.py", line 7, in <module>
    from .models import *
  File "/usr/local/lib/python3.5/dist-packages/spotify/models/__init__.py", line 7, in <module>
    from .playlist import Playlist
  File "/usr/local/lib/python3.5/dist-packages/spotify/models/playlist.py", line 29
    yield PlaylistTrack(self.__client, track)
    ^
SyntaxError: 'yield' inside async function

Any way to bypass this?

[Flask OAuth] User is not JSON serializable

Hi,

I'm still working on the flask OAuth example. I'm currently able to give my user consent and get to the redirect; flask is throwing an error on Object of type User is not JSON serializable.
FYI this is already including the two minor fixes I mention in the other two open issues, plus an added APP.secret_key on the flask program (required to be able to write/read from session)

* Serving Flask app "spotify_test3" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:8888/ (Press CTRL+C to quit)
127.0.0.1 - - [26/Sep/2019 17:09:36] "GET / HTTP/1.1" 302 -
[2019-09-26 17:09:38,659] ERROR in app: Exception on /spotify/callback [GET]
Traceback (most recent call last):
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
    return self.finalize_request(rv)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1969, in finalize_request
    response = self.process_response(response)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2268, in process_response
    self.session_interface.save_session(self, ctx.session, response)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/sessions.py", line 378, in save_session
    val = self.get_signing_serializer(app).dumps(dict(session))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 166, in dumps
    payload = want_bytes(self.dump_payload(obj))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/url_safe.py", line 42, in dump_payload
    json = super(URLSafeSerializerMixin, self).dump_payload(obj)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 133, in dump_payload
    return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/tag.py", line 305, in dumps
    return dumps(self.tag(value), separators=(",", ":"))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 211, in dumps
    rv = _json.dumps(obj, **kwargs)
  File "/usr/lib64/python3.7/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib64/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib64/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 100, in default
    return _json.JSONEncoder.default(self, o)
  File "/usr/lib64/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type User is not JSON serializable
[2019-09-26 17:09:38,669] ERROR in app: Request finalizing failed with an error while handling an error
Traceback (most recent call last):
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
    return self.finalize_request(rv)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1969, in finalize_request
    response = self.process_response(response)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2268, in process_response
    self.session_interface.save_session(self, ctx.session, response)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/sessions.py", line 378, in save_session
    val = self.get_signing_serializer(app).dumps(dict(session))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 166, in dumps
    payload = want_bytes(self.dump_payload(obj))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/url_safe.py", line 42, in dump_payload
    json = super(URLSafeSerializerMixin, self).dump_payload(obj)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 133, in dump_payload
    return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/tag.py", line 305, in dumps
    return dumps(self.tag(value), separators=(",", ":"))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 211, in dumps
    rv = _json.dumps(obj, **kwargs)
  File "/usr/lib64/python3.7/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib64/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib64/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 100, in default
    return _json.JSONEncoder.default(self, o)
  File "/usr/lib64/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type User is not JSON serializable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 1969, in finalize_request
    response = self.process_response(response)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/app.py", line 2268, in process_response
    self.session_interface.save_session(self, ctx.session, response)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/sessions.py", line 378, in save_session
    val = self.get_signing_serializer(app).dumps(dict(session))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 166, in dumps
    payload = want_bytes(self.dump_payload(obj))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/url_safe.py", line 42, in dump_payload
    json = super(URLSafeSerializerMixin, self).dump_payload(obj)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/itsdangerous/serializer.py", line 133, in dump_payload
    return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/tag.py", line 305, in dumps
    return dumps(self.tag(value), separators=(",", ":"))
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 211, in dumps
    rv = _json.dumps(obj, **kwargs)
  File "/usr/lib64/python3.7/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib64/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib64/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/flask/json/__init__.py", line 100, in default
    return _json.JSONEncoder.default(self, o)
  File "/usr/lib64/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type User is not JSON serializable
127.0.0.1 - - [26/Sep/2019 17:09:38] "GET /spotify/callback?code=AQAHJwiOILULHRJRKWkt_u-XP6kIYgmb4wAtHjW5pVkIhrW_rfYnse-YOosYb9TAjSV5Gusl4su2-T3Iqv3zXd_PILQNOA73WzJTlzlHmRsSnQbYKZLzHTCoXAMlyo7kKDtf6CzB4M-rrdwu3TYCT4Gdb6i0RlYcsPhnOv2H0aeaLv_GwqVL8VtwnKZ9DvpOWclE3N_pYBlMPuRx_z1Hu2VuQDRe7QQdtHMSYJMMvUC-dOEXTLOnSswWji94YfHFVb4iXLQhCizWQ1czS830I_b883MwoIseUwzAzBl4_kWMubT33Ue5CU5VvLtqhzOouQ HTTP/1.1" 500 -
127.0.0.1 - - [26/Sep/2019 17:09:41] "GET /favicon.ico HTTP/1.1" 404 -

Not possible to install spotify.py and pyspotify at the same time

Hi, I'm one of the maintainers of Mopidy and pyspotify.

After debugging with a Mopidy user recently, we found that their installation of Mopidy-Spotify, which is using pyspotify, was weirdly broken because the users also had spotify.py installed. The problem is that both projects use the spotify name for the top-level Python package, which makes it impossible to install both projects at the same time.

As pyspotify predates spotify.py by about eight years, I would very much appreciate it if you considered renaming your top-level package to something else than spotify, e.g. to spotifypy.

Thanks!

Error refreshing token

I noticed there is an error in the token refreshing code.

You are using the "Authorization": "Bearer " + access_token header but spotify wants you to use the "Authorization": f"Basic {token.decode()}" like is used your HttpClient.get_bearer_info function.

I worked around the problem by overloading User._refreshing_token with this code:

    async def _refreshing_token(self, expires: int, token: str):
        while True:
            import asyncio
            await asyncio.sleep(expires-1)
            REFRESH_TOKEN_URL = "https://accounts.spotify.com/api/token?grant_type=refresh_token&refresh_token={refresh_token}"
            route = ("POST", REFRESH_TOKEN_URL.format(refresh_token=token))
            from base64 import b64encode
            auth = b64encode(":".join((os.environ['SPOTIFY_CLIENT_ID'], os.environ['SPOTIFY_CLIENT_SECRET'])).encode())
            try:
                data = await self.client.http.request(
                    route,
                    headers={"Content-Type": "application/x-www-form-urlencoded",
                             "Authorization": f"Basic {auth.decode()}"}
                )

                expires = data["expires_in"]
                self.http.token = data["access_token"]
                print('token refreshed', data["access_token"])
            except:
                import traceback
                traceback.print_exc()

Also in I believe you want to change:
"Content-Type": kwargs.get("content_type", "application/json"),

to

"Content-Type": kwargs.pop("content_type", "application/json"),

in HTTPClient.request otherwise it errors when the content_type kwd is passed on.

I could do a pull request if you'd like. I really like your library, its very pythonic and much easier to use than the other spotify libraries I tried.

'Payload' referenced before assignment

UnboundLocalError: local variable 'payload' referenced before assignment is thrown when attempting to get a track from ID.

Further research into the source code points the error at line 1597 in track, where if market is not defined the payload variable isn't created.

Example with an async http server

Do you see an interest having examples with an async http server for authentication part ?

I struggle to have a working example with sanic but work is in progress.

Can't use parameters with user.top_artists()

Regardless of what parameters you set for user.top_artists(), such as:

**user.top_artists(limit=50,offset=0, time_range="long_term")**

You'll still get the default limit, offset and time range regardless of what you set the params to.
I managed to fix it locally by fiddling around with the library. I believe the problem is that the payload in the function top_artists_or_tracks(), is not sent the HTTP request.

I fixed it by changing line 854 in http.py from
return self.request(route)
to
return self.request(route, params=payload)

I'd make a pull request but I'm new at this stuff.

Problem with Sync module (failed call to super)

Hi!

I'm testing the flask example and I found this issue with the sync version of the library:

Python 3.7.4 (default, Jul 16 2019, 07:12:58) 
[GCC 9.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import spotify.sync as spotify
>>> spotify.Client('foo','bar')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/sync/models.py", line 81, in __init__
    super().__init__(*args, **kwargs)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/client.py", line 61, in __init__
    self.http = self._default_http_client(client_id, client_secret, loop=loop)
  File "/home/lollo/.local/share/virtualenvs/spotinator-I8qC83Ee/lib/python3.7/site-packages/spotify/sync/models.py", line 20, in __init__
    super().__init__(*args, **kwargs)
TypeError: super(type, obj): obj must be an instance or subtype of type
>>> 

explicitly specifying the base class seems to be fixing it (like this), but I'm not sure that's required.

aiohttp throws RuntimeError when running with Quart and ASGI server

I have created an API using Quart (async Flask) and spotify.py that has async functions as routes that make calls to spotify.search, spotify.get_artist, and other functions. When I run the API just using Quart, like with app.run(), everything works great. However, I am trying to deploy to Heroku, and in order to do that I need to use an ASGI web server called Hypercorn (similar to Gunicorn). When I run the API with hypercorn using hypercorn src.wsgi:app (where src/wsgi.py just contains an instance app of my API from a factory function), I get the following error on all routes:

ERROR in app: Exception on request GET /api/search/Drake
Traceback (most recent call last):
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 1451, in handle_request
    return await self.full_dispatch_request(request_context)
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 1473, in full_dispatch_request
    result = await self.handle_user_exception(error)
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 892, in handle_user_exception
    raise error
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 1471, in full_dispatch_request
    result = await self.dispatch_request(request_context)
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/quart/app.py", line 1519, in dispatch_request
    return await handler(**request_.view_args)
  File "/Users/shamaiengar/six-degrees-of-spotify/src/api.py", line 44, in search_artists
    results = await clients.spotify.search(artist_name, types=['artist'], limit="20")
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/spotify/client.py", line 256, in search
    data = await self.http.search(**kwargs)
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/spotify/http.py", line 123, in request
    self.bearer_info = bearer_info = await self.get_bearer_info()
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/spotify/http.py", line 103, in get_bearer_info
    async with self._session.post(**kwargs) as resp:
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/aiohttp/client.py", line 1005, in __aenter__
    self._resp = await self._coro
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/aiohttp/client.py", line 417, in _request
    with timer:
  File "/Users/shamaiengar/six-degrees-of-spotify/venv2/lib/python3.7/site-packages/aiohttp/helpers.py", line 568, in __enter__
    raise RuntimeError('Timeout context manager should be used '
RuntimeError: Timeout context manager should be used inside a task

I am using spotify.py 0.4.8 and Python 3.7.4. Could I get some advice on how to fix this?

If you want to reproduce this error, you should be able to fork my repo here, run the hypercorn command above, and try to access a route.

Right, so I've investigated the issue and have a possible fix ready.

Right, so I've investigated the issue and have a possible fix ready.
But first I'd like to address a couple points.

instead of waiting, justs tries requesting and exits with error code 439(Too many requests), when it should wait for the rate limit to pass.

The underlying http client does actually stall the request waiting for the rate limit to pass over. This issue here was that the stalling is performed only for that request and not all requests client wide resulting in a form of head of line blocking.

Additionally due to a large amount of requests made in a short period of time, which the library did not anticipate in the original design of the stalling mechanism, requests could essentially start "choking" each other out where one request finishes their rate limit stall but is then immediately rate limited again due to a sibling request taking its slot. The exception you received was a result of this following ten consecutive request failures all being rate limited.

When i do something like:
SNIP
And i get ratelimited

Unfortunately the API response does not let us know how many tries we have remaining in order to start performing client side throttling so the rate limit as a whole can be avoided. The response will only let us know if we've been rate limited or not and the library can only deal with that to the extent of waiting out the rate limit but not avoiding it.

If you do not want to hit the rate limit in the first place then you must throttle your own requests manually (this can be as simple as sleeping for a small amount of time between requests) or use multiple client applications as rate limits are applied on a per client app basis (although I do not know if this is endorsed by Spotify)

Right, so I've investigated the issue and have a possible fix ready.

I was a little reluctant to use the patch as it is right now since it feels inefficient (I performed 4096 search queries in four minutes with over 30 rate limits and I feel like that its possible to assume that either less time taken or less rate limits triggered but maybe not both) but I think its acceptable to release it under 0.8.6 temporarily and then succeed it with 0.8.7 as quickly as possible.

The fix that I've hacked together uses a barrier to stall all requests uniformly which gets controlled by the request that first gets rate limited. Unfortunately once the barrier gets lifted it's essentially a mad max free for all zerg rush for all stalled requests leading to more frequent rate limiting responses feeding a frequent stall, release, stall cycle until the amount of requests being made lowers enough to allow the API to recover from the clients madness.

Additionally that doesn't stop the possibility of choking out the entire pipeline leading to the same exception being raised, it merely delays it as much as possible. To combat this I've added a fallback stalling around the entire process, so when a request gets choked out the stalling falls back to an exponential backoff algorithm supplied by the new backoff dependency.

Ideally the two systems would get merged into one to avoid unnecessary overhead and allow some sort of slow bleed of requests back into the http session, but I'm not sure of how to tackle this and am experimenting in the dark for the time being.

Originally posted by @mental32 in #51 (comment)

edit_playlist issues when calling change_playlist_details

I am trying to change the description of a playlist on master.

    await user.edit_playlist(playlist, description=description)
  File "/home/frafra/.cache/pypoetry/virtualenvs/spotify-share-library-sjzLS3P8-py3.7/src/spotify/spotify/models/user.py", line 416, in edit_playlist
    await self.http.change_playlist_details(self.id, to_id(str(playlist)), **data)  # type: ignore
TypeError: change_playlist_details() takes 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given

TypeError: __await__() returned a coroutine

When attempting to run the Flask example on the main README I get the following error

/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/models/user.py:199: RuntimeWarning: coroutine 'WrappedUser.__await__' was never awaited
  return await cls.from_token(client, token, refresh_token)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/models/user.py:199: RuntimeWarning: coroutine 'User.from_token.<locals>.constructor' was never awaited
  return await cls.from_token(client, token, refresh_token)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
[2020-09-11 19:39:28,645] ERROR in app: Exception on /spotify/callback [GET]
Traceback (most recent call last):
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "test.py", line 30, in spotify_callback
    SPOTIFY_USERS[key] = spotify.User.from_code(
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/sync/models.py", line 45, in wrapped
    return self.__client_thread__.run_coroutine_threadsafe(
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/sync/thread.py", line 48, in run_coroutine_threadsafe
    return future.result()
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/concurrent/futures/_base.py", line 439, in result
    return self.__get_result()
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/concurrent/futures/_base.py", line 388, in __get_result
    raise self._exception
  File "/Users/user/miniconda3/envs/env22/lib/python3.8/site-packages/spotify/models/user.py", line 199, in from_code
    return await cls.from_token(client, token, refresh_token)
TypeError: __await__() returned a coroutine

I am running the following python environment:

Python 3.8.5
spotify 0.10.2
flask 1.1.2

Spotify.Player Resume not working.

When invoking .resume() on the Spotify.Player object, I recieve the following error:

  File "/Users//github/SpotiTerm-Rewrite/general.py", line 157, in controls
    _player.resume(device=chosen_device.id)
  File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/sync/models.py", line 42, in wrapper
    func(self, *args, **kwargs)
  File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/sync/thread.py", line 73, in run_coro
    raise err
  File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/models/player.py", line 93, in resume
    await self.user.http.play_playback(None, device_id=str(device))
  File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/http.py", line 1078, in play_playback
    f"`context_uri` must be a string or an iterable object, got {type(context_uri)}"
TypeError: `context_uri` must be a string or an iterable object, got <class 'NoneType'>

Can not use boolean for state in player.shuffle()

player.shuffle(state=True)

produces following error:

Traceback (most recent call last):
  File "./test.py", line 39, in <module>
    player.shuffle(state=True)
  File "/home/m3nix/.local/lib/python3.7/site-packages/spotify/sync/models.py", line 64, in wrapper
    func(self, *args, **kwargs)
  File "/home/m3nix/.local/lib/python3.7/site-packages/spotify/sync/thread.py", line 73, in run_coro
    raise err
  File "/home/m3nix/.local/lib/python3.7/site-packages/spotify/models/player.py", line 244, in shuffle
    await self.user.http.shuffle_playback(state, device_id=device_id)
  File "/home/m3nix/.local/lib/python3.7/site-packages/spotify/http.py", line 188, in request
    method, url, headers=headers, **kwargs
  File "/home/m3nix/.local/lib/python3.7/site-packages/aiohttp/client.py", line 473, in _request
    ssl=ssl, proxy_headers=proxy_headers, traces=traces)
  File "/home/m3nix/.local/lib/python3.7/site-packages/aiohttp/client_reqrep.py", line 263, in __init__
    url2 = url.with_query(params)
  File "/home/m3nix/.local/lib/python3.7/site-packages/yarl/__init__.py", line 885, in with_query
    new_query = self._get_str_query(*args, **kwargs)
  File "/home/m3nix/.local/lib/python3.7/site-packages/yarl/__init__.py", line 849, in _get_str_query
    quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query.items()
  File "/home/m3nix/.local/lib/python3.7/site-packages/yarl/__init__.py", line 849, in <genexpr>
    quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query.items()
  File "/home/m3nix/.local/lib/python3.7/site-packages/yarl/__init__.py", line 827, in _query_var
    "of type {}".format(v, type(v))
TypeError: Invalid variable type: value should be str or int, got True of type <class 'bool'>

using this instead works but is not the intended way I guess

player.shuffle(state="True")

Player.command: Device not found.

If I supply or don't supply the device parameter to Player.pause, resume, play etc. The error device not found is thrown.
It is strange as just before that, I perform user.get_devices() and retrieve the Spotify.Device object, which I pass directly to the parameter.

Full traceback:

  File "spotiterm.py", line 54, in main
    options[input(green("Select Option: "))](client, user)
  File "/Users/github/SpotiTerm-Rewrite/general.py", line 181, in player
    options[input(green("Select Option: "))](chosen_device, _player, user)
  File "/Users//github/SpotiTerm-Rewrite/general.py", line 154, in controls
    _player.pause()
  File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/sync/models.py", line 42, in wrapper
    func(self, *args, **kwargs)
  File "/Users/.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/sync/thread.py", line 73, in run_coro
    raise err
  File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/models/player.py", line 82, in pause
    await self.user.http.pause_playback(device_id=str(device))
  File "/Users//.local/share/virtualenvs/SpotiTerm-Rewrite-5Ht0qwiq/lib/python3.7/site-packages/spotify/http.py", line 205, in request
    raise NotFound(r, data)
spotify.errors.NotFound: Not Found (status code: 404): Device not found```

When creating a Playlist using the result of HttpClient.get_playlist() an error occurs

As per the title, When creating a spotify.Playlist from the result of getting a playlist using the HttpClient an error is produced.

Traceback (most recent call last):
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\discord\ext\commands\core.py", line 83, in wrapped
    ret = await coro(*args, **kwargs)
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\Life\cogs\voice\music.py", line 86, in play
    playlist = spotify.Playlist(self.bot.spotify, await self.bot.http_spotify.get_playlist(url_id))
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\playlist.py", line 143, in __init__
    self.__from_raw(data)
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\playlist.py", line 183, in __from_raw
    if "items" in data["tracks"]
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\playlist.py", line 182, in <genexpr>
    tuple(PlaylistTrack(client, item) for item in data["tracks"]["items"])
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\track.py", line 109, in __init__
    super().__init__(client, data["track"])
  File "C:\Users\mynam\Downloads\PycharmProjects\Life\venv\lib\site-packages\spotify\models\track.py", line 53, in __init__
    Artist(client, artist) for artist in data.pop("artists", [None])
AttributeError: 'NoneType' object has no attribute 'pop'

Player.progress_ms is only set once and not updated

Player.progress_ms is only set when the player is initialised. After the initialization the value will stay the same.

Expected behaviour: The progress_ms should always be the current value

I would propose one of the following:

  1. Don't use progress_ms at all. It is not really needed as the progress can easily be obtained with User.currently_playing()['progress_ms']
  2. Define a getter method with @property to fetch the current progress
  3. Wait until there is support for realtime playback state (see spotify/web-api#492). Maybe at some day there will be a websocket based solution publicly available. When this happens it would be possible to constantly refresh the progress_ms value with a coroutine for the websocket.

I think option 3 would be the best way. However it does not look like it will come soon.

Todo: Support new "Podcasts API"

There's a new set of endpoints dealing with podcasts, I keep forgetting to start work on support for it so hopefully opening this issue will keep me from forgetting again.

Example doesnt work

AttributeError: module 'spotify' has no attribute 'Client'

in searching example file

Receiving an error when trying to initialize player

When I run this code:

token = "user account token"
client_id = 'some client'
secret = 'some secret'

client = spotify.Client(client_id,secret)

async def main():
    user = await client.user_from_token(token)
    player = await user.get_player()


asyncio.get_event_loop().run_until_complete(main())

I receive the following error:

Warning (from warnings module):
  File "C:\Users\micha\AppData\Local\Programs\Python\Python38-32\lib\site-packages\spotify\client.py", line 164
    return await User.from_token(self, token)
RuntimeWarning: coroutine 'WrappedUser.__await__' was never awaited

Warning (from warnings module):
  File "C:\Users\micha\AppData\Local\Programs\Python\Python38-32\lib\site-packages\spotify\client.py", line 164
    return await User.from_token(self, token)
RuntimeWarning: coroutine 'User.from_token.<locals>.constructor' was never awaited
Traceback (most recent call last):
  File "E:/Projects/assistant/test.py", line 16, in <module>
    asyncio.get_event_loop().run_until_complete(main())
  File "C:\Users\micha\AppData\Local\Programs\Python\Python38-32\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "E:/Projects/assistant/test.py", line 12, in main
    user = await client.user_from_token(token)
  File "C:\Users\micha\AppData\Local\Programs\Python\Python38-32\lib\site-packages\spotify\client.py", line 164, in user_from_token
    return await User.from_token(self, token)
TypeError: __await__() returned a coroutine

Python: 3.8.5
Spotify: spotify-0.10.2

Invalid Syntax?

import spotify
import asyncio

def main():

    client = spotify.Client('SomeID', 'SomeSecret')
    results = await client.search('drake', limit=5, offset=20, market='JP')
    
if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())

I get this error:
SyntaxError: invalid syntax PS C:\Users\Cipher\Desktop\pythonfiles> python .\spotify.py File ".\spotify.py", line 8 results = await client.search('drake') ^ SyntaxError: invalid syntax PS C:\Users\Cipher\Desktop\pythonfiles> python .\spotify.py Traceback (most recent call last): File ".\spotify.py", line 11, in <module> asyncio.get_event_loop().run_until_complete(main()) File ".\spotify.py", line 7, in main client = spotify.Client('SomeID', 'SomeSecret') AttributeError: module 'spotify' has no attribute 'Client'

Please note this is my first time posting an issue but most of the code were copied from the examples to get a grasp of the API.

Thank you,

Cipher.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.