[GH-ISSUE #672] Querying my own user data without going throught auth2 #399

Closed
opened 2026-02-27 23:22:24 +03:00 by kerem · 32 comments
Owner

Originally created by @jiwidi on GitHub (Apr 18, 2021).
Original GitHub issue: https://github.com/spotipy-dev/spotipy/issues/672

Hi!

First, thanks for the hard work on the library, seems pretty cool.

I'm using the library and I would like to query my recent listened tracks. I know there is a method for that current_user_recently_played but it requires the spotipy client to have a current user, for that you have to go through a auth2 process (If I'm not mistaken). I would like to be able to query my own user recent songs without going through the manual authentication phase as I want to automate this in a script. If I could automate the authentication with a personal token that would work for me.

Is there a way to do that with this library? I thought I could probably generate an auth2 token from spotify and use it as seen in their example: https://developer.spotify.com/console/get-recently-played/?limit=&after=&before= but the tokens there expire in less than a day and I haven't seen any other way.

Best,
Jaime

Originally created by @jiwidi on GitHub (Apr 18, 2021). Original GitHub issue: https://github.com/spotipy-dev/spotipy/issues/672 Hi! First, thanks for the hard work on the library, seems pretty cool. I'm using the library and I would like to query my recent listened tracks. I know there is a method for that `current_user_recently_played` but it requires the spotipy client to have a current user, for that you have to go through a auth2 process (If I'm not mistaken). I would like to be able to query my own user recent songs without going through the manual authentication phase as I want to automate this in a script. If I could automate the authentication with a personal token that would work for me. Is there a way to do that with this library? I thought I could probably generate an auth2 token from spotify and use it as seen in their example: https://developer.spotify.com/console/get-recently-played/?limit=&after=&before= but the tokens there expire in less than a day and I haven't seen any other way. Best, Jaime
kerem 2026-02-27 23:22:24 +03:00
  • closed this issue
  • added the
    question
    label
Author
Owner

@stephanebruckert commented on GitHub (Apr 18, 2021):

You have to go through the oauth process just once. Then re-running the script at any time will use the refresh_token to build a new token that will be valid for 1 hour.

<!-- gh-comment-id:821997541 --> @stephanebruckert commented on GitHub (Apr 18, 2021): You _have_ to go through the oauth process just once. Then re-running the script at any time will use the refresh_token to build a new token that will be valid for 1 hour.
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

You have to go through the oauth process just once. Then re-running the script at any time will use the refresh_token to build a new token that will be valid for 1 hour.

That token will only be valid for 1 hour? Is there a way to extend it?

<!-- gh-comment-id:821997693 --> @jiwidi commented on GitHub (Apr 18, 2021): > You _have_ to go through the oauth process just once. Then re-running the script at any time will use the refresh_token to build a new token that will be valid for 1 hour. That token will only be valid for 1 hour? Is there a way to extend it?
Author
Owner

@stephanebruckert commented on GitHub (Apr 18, 2021):

It will automatically refresh so you won't have to worry about it

<!-- gh-comment-id:821997882 --> @stephanebruckert commented on GitHub (Apr 18, 2021): It will automatically refresh so you won't have to worry about it
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

It will automatically refresh so you won't have to worry about it

Sorry I didnt phrased it correctly. Is there any way to extend how long it will last so I dont have to refresh it every hour by running the script? I was thinking on running this script daily but will have to cut it down to hourly if the token can only last 1 hour without refresh

<!-- gh-comment-id:821998173 --> @jiwidi commented on GitHub (Apr 18, 2021): > It will automatically refresh so you won't have to worry about it Sorry I didnt phrased it correctly. Is there any way to extend how long it will last so I dont have to refresh it every hour by running the script? I was thinking on running this script daily but will have to cut it down to hourly if the token can only last 1 hour without refresh
Author
Owner

@stephanebruckert commented on GitHub (Apr 18, 2021):

If the token expires, spotipy will automatically use the refresh token to retrieve a new token. And that refresh_token never expires! In your case running it daily will just work

<!-- gh-comment-id:821998726 --> @stephanebruckert commented on GitHub (Apr 18, 2021): If the token expires, spotipy will automatically use the refresh token to retrieve a new token. And that refresh_token never expires! In your case running it daily will just work
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

Hi again,

How should I instantiate the spotipy client for it to use the refresh token? My token just expired and it didnt automatically ask for refresh token.

Script code:

import spotipy
import json
import os
import time

token = os.environ['SPOTIFY_TOKEN']


sp = spotipy.Spotify(auth=token)
results = sp.current_user_recently_played()
results["date_run"] = time.time()
with open(".github/spotify_query_results.json", "w+") as f:
    json.dump(results,f)

The token I'm using there I got it from the following script:

import sys
import spotipy
import spotipy.util as util

scope = 'user-read-recently-played'
username = 'xxx'
token = util.prompt_for_user_token(username, scope, redirect_uri="http://localhost:8888/callback/")

print(token)

Should I instantiate spotipy client differently in my script? Or what am I missing so the client refreshes the token by itself.

<!-- gh-comment-id:822003793 --> @jiwidi commented on GitHub (Apr 18, 2021): Hi again, How should I instantiate the spotipy client for it to use the refresh token? My token just expired and it didnt automatically ask for refresh token. Script code: ```python import spotipy import json import os import time token = os.environ['SPOTIFY_TOKEN'] sp = spotipy.Spotify(auth=token) results = sp.current_user_recently_played() results["date_run"] = time.time() with open(".github/spotify_query_results.json", "w+") as f: json.dump(results,f) ``` The token I'm using there I got it from the following script: ```python import sys import spotipy import spotipy.util as util scope = 'user-read-recently-played' username = 'xxx' token = util.prompt_for_user_token(username, scope, redirect_uri="http://localhost:8888/callback/") print(token) ``` Should I instantiate spotipy client differently in my script? Or what am I missing so the client refreshes the token by itself.
Author
Owner

@Peter-Schorn commented on GitHub (Apr 18, 2021):

Don't use prompt_for_user_token; it's been deprecated because it does not automatically refresh the access token. Instead, do this:

from spotipy import Spotify, SpotifyOAuth, CacheFileHandler

username = "your username"
scope = "user-read-recently-played"
redirect_uri = "your redirect uri"

spotify = Spotify(
    auth_manager=SpotifyOAuth(
        redirect_uri=redirect_uri,
        scope=scope,
        cache_handler=CacheFileHandler(username=username)
    )
)

results = spotify.current_user_recently_played()

print(results)

The token info will be saved to a file and automatically refreshed when necessary.

<!-- gh-comment-id:822021634 --> @Peter-Schorn commented on GitHub (Apr 18, 2021): Don't use `prompt_for_user_token`; it's been deprecated because it does not automatically refresh the access token. Instead, do this: ```python from spotipy import Spotify, SpotifyOAuth, CacheFileHandler username = "your username" scope = "user-read-recently-played" redirect_uri = "your redirect uri" spotify = Spotify( auth_manager=SpotifyOAuth( redirect_uri=redirect_uri, scope=scope, cache_handler=CacheFileHandler(username=username) ) ) results = spotify.current_user_recently_played() print(results) ``` The token info will be saved to a file and automatically refreshed when necessary.
Author
Owner

@stephanebruckert commented on GitHub (Apr 18, 2021):

Yes ⬆️

I also see you are passing the token through an environment variable and I guess you are doing that to pass the token from your local machine to the server? Depending on where your app will be deployed, you will have to adapt the code a bit:

  • use open_browser=False if you have access to a terminal on the server side, this way the login URL will be printed instead of opened,
  • if the server doesn't give access to a terminal, you will have to run the code first on your local machine, and then make sure the server can access that token. You might need a customized cache handler.

Where do you plan to deploy your code?

<!-- gh-comment-id:822028867 --> @stephanebruckert commented on GitHub (Apr 18, 2021): Yes ⬆️ I also see you are passing the token through an environment variable and I guess you are doing that to pass the token from your local machine to the server? Depending on where your app will be deployed, you will have to adapt the code a bit: - use `open_browser=False` [if you have access to a terminal](https://github.com/plamere/spotipy/blob/master/FAQ.md#how-do-i-obtain-authorization-in-a-headlessbrowserless-environment) on the server side, this way the login URL will be printed instead of opened, - if the server doesn't give access to a terminal, you will have to run the code first on your local machine, and then make sure the server can access that token. You might need a [customized cache handler](https://github.com/plamere/spotipy/blob/master/FAQ.md#how-can-i-store-tokens-in-a-database-rather-than-on-the-filesystem). Where do you plan to deploy your code?
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

Don't use prompt_for_user_token; it's been deprecated because it does not automatically refresh the access token. Instead, do this:

from spotipy import Spotify, SpotifyOAuth, CacheFileHandler

username = "your username"
scope = "user-read-recently-played"
redirect_uri = "your redirect uri"

spotify = Spotify(
    auth_manager=SpotifyOAuth(
        redirect_uri=redirect_uri,
        scope=scope,
        cache_handler=CacheFileHandler(username=username)
    )
)

results = spotify.current_user_recently_played()

print(results)

The token info will be saved to a file and automatically refreshed when necessary.

I see, for what I have just tested if that file is missing it will ask for authentication again right? I'm planning to run this script with github actions so its going to be a spin up VM and state won't be maintained between runs (neither I want to save the file in the repo)

Is there any way to save the refresh token and pass it to the SpotifyOAuth/CacheFileHandler every time? This way I can save the refreshtoken as a github repository secret and the main token will be refreshed every execution.

edit: Now that I think about it there could be a workaround where I save the whole file content as a secret in the github repository, save it as a file in running time and save the values again when finished. This is an option in case the refresh token secret can't work.

<!-- gh-comment-id:822029266 --> @jiwidi commented on GitHub (Apr 18, 2021): > Don't use `prompt_for_user_token`; it's been deprecated because it does not automatically refresh the access token. Instead, do this: > > ```python > from spotipy import Spotify, SpotifyOAuth, CacheFileHandler > > username = "your username" > scope = "user-read-recently-played" > redirect_uri = "your redirect uri" > > spotify = Spotify( > auth_manager=SpotifyOAuth( > redirect_uri=redirect_uri, > scope=scope, > cache_handler=CacheFileHandler(username=username) > ) > ) > > results = spotify.current_user_recently_played() > > print(results) > ``` > > The token info will be saved to a file and automatically refreshed when necessary. I see, for what I have just tested if that file is missing it will ask for authentication again right? I'm planning to run this script with github actions so its going to be a spin up VM and state won't be maintained between runs (neither I want to save the file in the repo) Is there any way to save the refresh token and pass it to the SpotifyOAuth/CacheFileHandler every time? This way I can save the refreshtoken as a github repository secret and the main token will be refreshed every execution. edit: Now that I think about it there could be a workaround where I save the whole file content as a secret in the github repository, save it as a file in running time and save the values again when finished. This is an option in case the refresh token secret can't work.
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

Yes

I also see you are passing the token through an environment variable and I guess you are doing that to pass the token from your local machine to the server? Depending on where your app will be deployed, you will have to adapt the code a bit:

  • use open_browser=False if you have access to a terminal on the server side, this way the login URL will be printed instead of opened,
  • if the server doesn't give access to a terminal, you will have to run the code first on your local machine, and then make sure the server can access that token. You might need a customized cache handler.

Where do you plan to deploy your code?

I plan to run it with github actions so saving into a common cache file shared between executions is a no go. Unless there is a way to pass the refresh token to SpotifyOAuth(then I can use github repo secrets to do so) instead from reading from a file I think is hard.

The other idea is to save the whole file content as a secret and save it to file during script execution time.

<!-- gh-comment-id:822030520 --> @jiwidi commented on GitHub (Apr 18, 2021): > Yes > > I also see you are passing the token through an environment variable and I guess you are doing that to pass the token from your local machine to the server? Depending on where your app will be deployed, you will have to adapt the code a bit: > > * use `open_browser=False` [if you have access to a terminal](https://github.com/plamere/spotipy/blob/master/FAQ.md#how-do-i-obtain-authorization-in-a-headlessbrowserless-environment) on the server side, this way the login URL will be printed instead of opened, > * if the server doesn't give access to a terminal, you will have to run the code first on your local machine, and then make sure the server can access that token. You might need a [customized cache handler](https://github.com/plamere/spotipy/blob/master/FAQ.md#how-can-i-store-tokens-in-a-database-rather-than-on-the-filesystem). > > Where do you plan to deploy your code? I plan to run it with github actions so saving into a common cache file shared between executions is a no go. Unless there is a way to pass the refresh token to SpotifyOAuth(then I can use github repo secrets to do so) instead from reading from a file I think is hard. The other idea is to save the whole file content as a secret and save it to file during script execution time.
Author
Owner

@stephanebruckert commented on GitHub (Apr 18, 2021):

The github action use case is an interesting one... because indeed the state is not saved between runs. I am not certain that using the same refresh_token as a GHA secret will work, I would bet that it is invalidated as soon as it is used.

Here is another suggestion... It's usually not something I would recommend for production code, but it looks perfect for the CI use case. There is a way to get a user token that is valid for 1 year (instead of 1 hour). It has to be manually retrieved from the cookies. Have a look at https://github.com/enriquegh/spotify-webplayer-token. You could just store it as a GHA secret and then pass it to spotipy as you initially did:

token = os.environ['SPOTIFY_TOKEN']

spotipy.Spotify(auth=token)
<!-- gh-comment-id:822033137 --> @stephanebruckert commented on GitHub (Apr 18, 2021): The github action use case is an interesting one... because indeed the state is not saved between runs. I am not certain that using the same refresh_token as a GHA secret will work, I would bet that it is invalidated as soon as it is used. Here is another suggestion... It's usually not something I would recommend for production code, but it looks perfect for the CI use case. There is a way to get a user token that is valid for 1 year (instead of 1 hour). It has to be manually retrieved from the cookies. Have a look at https://github.com/enriquegh/spotify-webplayer-token. You could just store it as a GHA secret and then pass it to spotipy as you initially did: token = os.environ['SPOTIFY_TOKEN'] spotipy.Spotify(auth=token)
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

The github action use case is an interesting one... because indeed the state is not saved between runs. I am not certain that using the same refresh_token as a GHA secret will work, I would bet that it is invalidated as soon as it is used.

Here is another suggestion... It's usually not something I would recommend for production code, but it looks perfect for the CI use case. There is a way to get a user token that is valid for 1 year (instead of 1 hour). It has to be manually retrieved from the cookies. Have a look at https://github.com/enriquegh/spotify-webplayer-token. You could just store it as a GHA secret and then pass it to spotipy as you initially did:

token = os.environ['SPOTIFY_TOKEN']

spotipy.Spotify(auth=token)

That is a good solution yeah, if I can't make the secrets work will probably go with that one. I think I could update the secret with a new refresh token if that is generated when one is used. Is the .cache-username file containing REFRESH_TOKEN updated with new refresh token when this one is used?

I was thinking how could the github action look and this is so far what I have

      # Create cache files for spotipy auth
      - name: Cache auth spotipy
        run: echo ${{ secrets.CACHE_JIWIDI }} > .github/.cache-jiwidi

      # Run our spotify query
      - name: Run spotify query
        run: python .github/queryspotify.py
        env:
          SPOTIPY_CLIENT_SECRET=: ${{ secrets.SPOTIPY_CLIENT_SECRET}}
          SPOTIPY_CLIENT_ID=: ${{ secrets.SPOTIPY_CLIENT_ID}}
          SPOTIPY_REDIRECT_URI=: ${{ secrets.SPOTIPY_REDIRECT_URI}}

      # UPDATE SECRET
      - name: Update Cache auth spotipy
        run: ##SOME CODE THAT READS .cache-jiwidi AND UPDATES CACHE_JIWIDI SECRET WITH THE CONTENT
<!-- gh-comment-id:822034067 --> @jiwidi commented on GitHub (Apr 18, 2021): > The github action use case is an interesting one... because indeed the state is not saved between runs. I am not certain that using the same refresh_token as a GHA secret will work, I would bet that it is invalidated as soon as it is used. > > Here is another suggestion... It's usually not something I would recommend for production code, but it looks perfect for the CI use case. There is a way to get a user token that is valid for 1 year (instead of 1 hour). It has to be manually retrieved from the cookies. Have a look at https://github.com/enriquegh/spotify-webplayer-token. You could just store it as a GHA secret and then pass it to spotipy as you initially did: > > ``` > token = os.environ['SPOTIFY_TOKEN'] > > spotipy.Spotify(auth=token) > ``` That is a good solution yeah, if I can't make the secrets work will probably go with that one. I think I could update the secret with a new refresh token if that is generated when one is used. Is the `.cache-username` file containing REFRESH_TOKEN updated with new refresh token when this one is used? I was thinking how could the github action look and this is so far what I have ``` # Create cache files for spotipy auth - name: Cache auth spotipy run: echo ${{ secrets.CACHE_JIWIDI }} > .github/.cache-jiwidi # Run our spotify query - name: Run spotify query run: python .github/queryspotify.py env: SPOTIPY_CLIENT_SECRET=: ${{ secrets.SPOTIPY_CLIENT_SECRET}} SPOTIPY_CLIENT_ID=: ${{ secrets.SPOTIPY_CLIENT_ID}} SPOTIPY_REDIRECT_URI=: ${{ secrets.SPOTIPY_REDIRECT_URI}} # UPDATE SECRET - name: Update Cache auth spotipy run: ##SOME CODE THAT READS .cache-jiwidi AND UPDATES CACHE_JIWIDI SECRET WITH THE CONTENT ```
Author
Owner

@Peter-Schorn commented on GitHub (Apr 18, 2021):

I don't think it's possible to update the value of a secret during/after the execution of a workflow. The github docs don't say anything about this. Saving the token info to a file in the repository during the workflow is pointless because the file won't persist after the workflow completes.

<!-- gh-comment-id:822036060 --> @Peter-Schorn commented on GitHub (Apr 18, 2021): I don't think it's possible to update the value of a secret during/after the execution of a workflow. The github docs don't say anything about this. Saving the token info to a file in the repository during the workflow is pointless because the file won't persist after the workflow completes.
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

I don't think it's possible to update the value of a secret during/after the execution of a workflow. The github docs don't say anything about this.

With a quick Google search I found some actions that do it: https://github.com/hmanzur/actions-set-secret. Haven't gone too much detailed inside the code but I guess I could get the required code to do what I suggested.

Edit: quick way to make it work would be to just replace the value passes to the action with the result of "cat cache file"

<!-- gh-comment-id:822036601 --> @jiwidi commented on GitHub (Apr 18, 2021): > I don't think it's possible to update the value of a secret during/after the execution of a workflow. The github docs don't say anything about this. With a quick Google search I found some actions that do it: https://github.com/hmanzur/actions-set-secret. Haven't gone too much detailed inside the code but I guess I could get the required code to do what I suggested. Edit: quick way to make it work would be to just replace the value passes to the action with the result of "cat cache file"
Author
Owner

@Peter-Schorn commented on GitHub (Apr 18, 2021):

With a quick Google search I found some actions that do it: https://github.com/hmanzur/actions-set-secret.

Well, then, this makes things much simpler. All you need to do is create a custom cache handler that retrieves and saves the token info to the secrets API.

from spotipy import Spotify, SpotifyOAuth, CacheHandler
import json

class SecretsCacheHandler(CacheHandler):

    def save_token_to_cache(self, token_info):
        # `token_info` will be a python dict. convert it to a JSON string.
        token_string = json.dumps(token_info)
        # save the `token_string` to your secrets

    def get_cached_token(self):
        token_string = # retrieve the token info string from your secrets
        
        # convert the string back to a dict and return it
        token_info = json.loads(token_string)
        return token_info

Then create your instance of Spotify using the cache handler:

scope = "user-read-recently-played"
spotify = Spotify(
    auth_manager=SpotifyOAuth(
        scope=scope,
        cache_handler=SecretsCacheHandler()
    )
)
<!-- gh-comment-id:822038324 --> @Peter-Schorn commented on GitHub (Apr 18, 2021): > With a quick Google search I found some actions that do it: https://github.com/hmanzur/actions-set-secret. Well, then, this makes things much simpler. All you need to do is create a [custom cache handler](https://github.com/plamere/spotipy/blob/master/FAQ.md#how-can-i-store-tokens-in-a-database-rather-than-on-the-filesystem) that retrieves and saves the token info to the secrets API. ```python from spotipy import Spotify, SpotifyOAuth, CacheHandler import json class SecretsCacheHandler(CacheHandler): def save_token_to_cache(self, token_info): # `token_info` will be a python dict. convert it to a JSON string. token_string = json.dumps(token_info) # save the `token_string` to your secrets def get_cached_token(self): token_string = # retrieve the token info string from your secrets # convert the string back to a dict and return it token_info = json.loads(token_string) return token_info ``` Then create your instance of `Spotify` using the cache handler: ```python scope = "user-read-recently-played" spotify = Spotify( auth_manager=SpotifyOAuth( scope=scope, cache_handler=SecretsCacheHandler() ) ) ```
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

With a quick Google search I found some actions that do it: https://github.com/hmanzur/actions-set-secret.

Well, then, this makes things much simpler. All you need to do is create a custom cache handler that retrieves and saves the token info to the secrets API.


from spotipy import Spotify, SpotifyOAuth, CacheHandler

import json



class SecretsCacheHandler(CacheHandler):



    def save_token_to_cache(self, token_info):

        # `token_info` will be a python dict. convert it to a JSON string.

        token_string = json.dumps(token_info)

        # save the `token_string` to your secrets



    def get_cached_token(self):

        token_string = # retrieve the token info string from your secrets

        

        # convert the string back to a dict and return it

        token_info = json.loads(token_string)

        return token_info

Then create your instance of Spotify using the cache handler:


scope = "user-read-recently-played"

spotify = Spotify(

    auth_manager=SpotifyOAuth(

        scope=scope,

        cache_handler=SecretsCacheHandler()

    )

)

Such a clean solution! Will try implement it :)

<!-- gh-comment-id:822040501 --> @jiwidi commented on GitHub (Apr 18, 2021): > > With a quick Google search I found some actions that do it: https://github.com/hmanzur/actions-set-secret. > > > > Well, then, this makes things much simpler. All you need to do is create a [custom cache handler](https://github.com/plamere/spotipy/blob/master/FAQ.md#how-can-i-store-tokens-in-a-database-rather-than-on-the-filesystem) that retrieves and saves the token info to the secrets API. > > > > ```python > > from spotipy import Spotify, SpotifyOAuth, CacheHandler > > import json > > > > class SecretsCacheHandler(CacheHandler): > > > > def save_token_to_cache(self, token_info): > > # `token_info` will be a python dict. convert it to a JSON string. > > token_string = json.dumps(token_info) > > # save the `token_string` to your secrets > > > > def get_cached_token(self): > > token_string = # retrieve the token info string from your secrets > > > > # convert the string back to a dict and return it > > token_info = json.loads(token_string) > > return token_info > > ``` > > Then create your instance of `Spotify` using the cache handler: > > ```python > > scope = "user-read-recently-played" > > spotify = Spotify( > > auth_manager=SpotifyOAuth( > > scope=scope, > > cache_handler=SecretsCacheHandler() > > ) > > ) > > ``` Such a clean solution! Will try implement it :)
Author
Owner

@Peter-Schorn commented on GitHub (Apr 18, 2021):

You'll have to use the secrets API directly because you need to call it from within your python script (as opposed to using a github action).

<!-- gh-comment-id:822041299 --> @Peter-Schorn commented on GitHub (Apr 18, 2021): You'll have to use the [secrets API](https://docs.github.com/en/rest/reference/actions#secrets) directly because you need to call it from within your python script (as opposed to using a github action).
Author
Owner

@stephanebruckert commented on GitHub (Apr 18, 2021):

@Peter-Schorn great idea!

@jiwidi if you manage to do this, feel free to open a PR to add a GithubSecretsCacheHandler.

This is something we could even use for Github Actions in the current repo, as we are not currently running integration tests automatically.

<!-- gh-comment-id:822042259 --> @stephanebruckert commented on GitHub (Apr 18, 2021): @Peter-Schorn great idea! @jiwidi if you manage to do this, feel free to open a PR to add a GithubSecretsCacheHandler. This is something we could even use for Github Actions in the current repo, as we are not currently running integration tests automatically.
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

@Peter-Schorn great idea!

@jiwidi if you manage to do this, feel free to open a PR to add a GithubSecretsCacheHandler.

This is something we could even use for Github Actions in the current repo, as we are not currently running integration tests automatically.

Sure, will do!

<!-- gh-comment-id:822042433 --> @jiwidi commented on GitHub (Apr 18, 2021): > @Peter-Schorn great idea! > > > > @jiwidi if you manage to do this, feel free to open a PR to add a GithubSecretsCacheHandler. > > > > This is something we could even use for Github Actions in the current repo, as we are not currently running integration tests automatically. Sure, will do!
Author
Owner

@Peter-Schorn commented on GitHub (Apr 18, 2021):

It looks like there is a python wrapper for the github API: https://github.com/PyGithub/PyGithub

<!-- gh-comment-id:822044452 --> @Peter-Schorn commented on GitHub (Apr 18, 2021): It looks like there is a python wrapper for the github API: https://github.com/PyGithub/PyGithub
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

@Peter-Schorn @stefanondisponibile

I'm now working in the implementation and it can be done in different ways, with a PR in mind to add this secretCacheHandler, would you guys prefer to do the requests manually or use the github api?

Both have benefits, without the github API you don't add an extra requirement and without it your request is easier to become obsolete in the future as the API would presumably keep updating.

I really don't mind which but since it will go for PR you probably have some preferences

<!-- gh-comment-id:822045780 --> @jiwidi commented on GitHub (Apr 18, 2021): @Peter-Schorn @stefanondisponibile I'm now working in the implementation and it can be done in different ways, with a PR in mind to add this secretCacheHandler, would you guys prefer to do the requests manually or use the github api? Both have benefits, without the github API you don't add an extra requirement and without it your request is easier to become obsolete in the future as the API would presumably keep updating. I really don't mind which but since it will go for PR you probably have some preferences
Author
Owner

@stephanebruckert commented on GitHub (Apr 18, 2021):

Just use the library, that's what it's for!

<!-- gh-comment-id:822045997 --> @stephanebruckert commented on GitHub (Apr 18, 2021): Just use the library, that's what it's for!
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

I think I found a bug on their API because they list the create_secret() method and define it in the code but the callable is missing. Maybe the missed to include it in a init file or something, opened an issue on their repo https://github.com/PyGithub/PyGithub/issues/1923

Hopefully I missed something and is an easy fix :/

<!-- gh-comment-id:822054591 --> @jiwidi commented on GitHub (Apr 18, 2021): I think I found a bug on their API because they list the `create_secret()` method and define it in the code but the callable is missing. Maybe the missed to include it in a init file or something, opened an issue on their repo https://github.com/PyGithub/PyGithub/issues/1923 Hopefully I missed something and is an easy fix :/
Author
Owner

@Peter-Schorn commented on GitHub (Apr 18, 2021):

It's not a bug; you're looking at an unreleased version of the repository. To install the latest commit from master:

pip uninstall PyGithub
pip install git+git://github.com/PyGithub/PyGithub@master
<!-- gh-comment-id:822055765 --> @Peter-Schorn commented on GitHub (Apr 18, 2021): It's not a bug; you're looking at an unreleased version of the repository. To install the latest commit from master: ``` pip uninstall PyGithub pip install git+git://github.com/PyGithub/PyGithub@master ```
Author
Owner

@jiwidi commented on GitHub (Apr 18, 2021):

It's not a bug; you're looking at an unreleased version of the repository. To install the latest commit from master:

pip uninstall PyGithub
pip install git+git://github.com/PyGithub/PyGithub@master

I thought the documentation would only include changes from the latest released versions, mistake then!

<!-- gh-comment-id:822055971 --> @jiwidi commented on GitHub (Apr 18, 2021): > It's not a bug; you're looking at an unreleased version of the repository. To install the latest commit from master: > > ``` > pip uninstall PyGithub > pip install git+git://github.com/PyGithub/PyGithub@master > ``` I thought the documentation would only include changes from the latest released versions, mistake then!
Author
Owner

@felix-hilden commented on GitHub (Apr 19, 2021):

Just to give my two cents, I've used the same Spotify refresh token for API testing for essentially over a year now with daily runs. It truly does never expire! There's no limit on how many tokens can be requested, hence it's great utility for serialisation and databases. PKCE refresh tokens do expire after the first refresh though, so don't use them.

<!-- gh-comment-id:822189124 --> @felix-hilden commented on GitHub (Apr 19, 2021): Just to give my two cents, I've used the same Spotify refresh token for API testing for essentially over a year now with daily runs. It truly does never expire! There's no limit on how many tokens can be requested, hence it's great utility for serialisation and databases. PKCE refresh tokens do expire after the first refresh though, so don't use *them*.
Author
Owner

@jiwidi commented on GitHub (Apr 19, 2021):

So I have a working script now:

from spotipy import Spotify, SpotifyOAuth, CacheHandler
import time
import json
import os
from github import Github

class SecretsCacheHandler(CacheHandler):
    def __init__(self, repository_name, github_access_token=None, github_access_token_secret_name=None, secret_cache_name="SPOTIPY_CACHE"):
        CacheHandler.__init__(self)
        #Create github api client from token or secret name containing token
        if(github_access_token!=None):
            self.github_client = Github(github_access_token)
        elif(github_access_token_secret_name!=None):
            self.github_client = Github(os.environ[github_access_token_secret_name])
        else:
            raise Exception("You must provide token or secret name containing your github access token in order to instantiate the cache handler")

        self.secret_cache_name = secret_cache_name
        self.repository = self.github_client.get_repo(repository_name)

    def save_token_to_cache(self, token_info):
        # `token_info` will be a python dict. convert it to a JSON string.
        token_string = json.dumps(token_info)
        # save the `token_string` to your secrets
        self.repository.create_secret(secret_name=self.secret_cache_name,unencrypted_value=token_string)

    def get_cached_token(self):
        #Retrieve secret from enviorment
        token_string = os.environ[self.secret_cache_name]

        # Convert the string back to a dict and return it
        token_info = json.loads(token_string)

        return token_info


username = "jiwidi"
scope = "user-read-recently-played"

spotify = Spotify(
    auth_manager=SpotifyOAuth(
        scope=scope,
        cache_handler=SecretsCacheHandler(repository_name="jiwidi/jiwidi.github.io", github_access_token_secret_name="MAIN_TOKEN"),
    )
)

results = spotify.current_user_recently_played()
results["date_run"] = time.time()

with open(".github/spotify_query_results.json", "w+") as f:
    json.dump(results,f)

And the github action that triggers it:

name: SPOTIFYQUERY

# Controls when the action will run.
on:
  #Run every day at midnight
  schedule:
    - cron: "0 0 * * *"

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2

      # Set up python environment
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.8

      - name: Set up python libraries
        run: python -m pip install requests spotipy==2.18

      - name: Latest pytigt
        run: python -m pip install git+git://github.com/PyGithub/PyGithub@master

      # Run our spotify query with custom secretCacheHandler
      - name: Run spotify query
        run: python .github/queryspotify.py
        env:
          SPOTIPY_CLIENT_SECRET: ${{ secrets.SPOTIPY_CLIENT_SECRET}}
          SPOTIPY_CLIENT_ID: ${{ secrets.SPOTIPY_CLIENT_ID}}
          SPOTIPY_REDIRECT_URI: ${{ secrets.SPOTIPY_REDIRECT_URI}}
          SPOTIPY_CACHE: "${{ secrets.SPOTIPY_CACHE}}"
          MAIN_TOKEN: '${{ secrets.MAIN_TOKEN}}'

      - name: Commit and Push
        run: |
          git config --global user.name "github-actions[bot]"
          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add -A
          git commit -m "A robot updating spotify query results. Beep beep boop"
          git push

Will do some more testing and open a PR if all is good.

<!-- gh-comment-id:822348788 --> @jiwidi commented on GitHub (Apr 19, 2021): So I have a working script now: ```python from spotipy import Spotify, SpotifyOAuth, CacheHandler import time import json import os from github import Github class SecretsCacheHandler(CacheHandler): def __init__(self, repository_name, github_access_token=None, github_access_token_secret_name=None, secret_cache_name="SPOTIPY_CACHE"): CacheHandler.__init__(self) #Create github api client from token or secret name containing token if(github_access_token!=None): self.github_client = Github(github_access_token) elif(github_access_token_secret_name!=None): self.github_client = Github(os.environ[github_access_token_secret_name]) else: raise Exception("You must provide token or secret name containing your github access token in order to instantiate the cache handler") self.secret_cache_name = secret_cache_name self.repository = self.github_client.get_repo(repository_name) def save_token_to_cache(self, token_info): # `token_info` will be a python dict. convert it to a JSON string. token_string = json.dumps(token_info) # save the `token_string` to your secrets self.repository.create_secret(secret_name=self.secret_cache_name,unencrypted_value=token_string) def get_cached_token(self): #Retrieve secret from enviorment token_string = os.environ[self.secret_cache_name] # Convert the string back to a dict and return it token_info = json.loads(token_string) return token_info username = "jiwidi" scope = "user-read-recently-played" spotify = Spotify( auth_manager=SpotifyOAuth( scope=scope, cache_handler=SecretsCacheHandler(repository_name="jiwidi/jiwidi.github.io", github_access_token_secret_name="MAIN_TOKEN"), ) ) results = spotify.current_user_recently_played() results["date_run"] = time.time() with open(".github/spotify_query_results.json", "w+") as f: json.dump(results,f) ``` And the github action that triggers it: ```yaml name: SPOTIFYQUERY # Controls when the action will run. on: #Run every day at midnight schedule: - cron: "0 0 * * *" # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 # Set up python environment - name: Set up Python uses: actions/setup-python@v2 with: python-version: 3.8 - name: Set up python libraries run: python -m pip install requests spotipy==2.18 - name: Latest pytigt run: python -m pip install git+git://github.com/PyGithub/PyGithub@master # Run our spotify query with custom secretCacheHandler - name: Run spotify query run: python .github/queryspotify.py env: SPOTIPY_CLIENT_SECRET: ${{ secrets.SPOTIPY_CLIENT_SECRET}} SPOTIPY_CLIENT_ID: ${{ secrets.SPOTIPY_CLIENT_ID}} SPOTIPY_REDIRECT_URI: ${{ secrets.SPOTIPY_REDIRECT_URI}} SPOTIPY_CACHE: "${{ secrets.SPOTIPY_CACHE}}" MAIN_TOKEN: '${{ secrets.MAIN_TOKEN}}' - name: Commit and Push run: | git config --global user.name "github-actions[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" git add -A git commit -m "A robot updating spotify query results. Beep beep boop" git push ``` Will do some more testing and open a PR if all is good.
Author
Owner

@Peter-Schorn commented on GitHub (Apr 19, 2021):

You need to store the token info as a property in your cache handler. In your version, after the token gets refreshed, you're still retrieving the old token info from the environment in get_cached_token. This means that the token will get refreshed before every single call to the Spotify web API.

<!-- gh-comment-id:822556536 --> @Peter-Schorn commented on GitHub (Apr 19, 2021): You need to store the token info as a property in your cache handler. In your version, after the token gets refreshed, you're still retrieving the old token info from the environment in `get_cached_token`. This means that the token will get refreshed before every single call to the Spotify web API.
Author
Owner

@jiwidi commented on GitHub (Apr 21, 2021):

@Peter-Schorn Sorry I dont follow.

Shouldnt get_cached_token retrieve it from enviorment? In what order are save_token_to_cache and get_cached_token called? I thought get_cached_token will be called first (meaning you need to read the secret) and after that the token would get refreshed, then the manager calls save_token_to_cache with the new value and not the old one from get_cached_token

<!-- gh-comment-id:823964612 --> @jiwidi commented on GitHub (Apr 21, 2021): @Peter-Schorn Sorry I dont follow. Shouldnt `get_cached_token` retrieve it from enviorment? In what order are `save_token_to_cache` and `get_cached_token` called? I thought `get_cached_token` will be called first (meaning you need to read the secret) and after that the token would get refreshed, then the manager calls `save_token_to_cache` with the new value and not the old one from `get_cached_token`
Author
Owner

@Peter-Schorn commented on GitHub (Apr 21, 2021):

See my version and the tests for it.

Here's what happens in your version:

  1. In the setup for the workflow, the token info string is retrieved from the SPOTIPY_CACHE secret and stored in the environment variable of the same name, although these aren't the same things. This is the only time that this environment variable is set.
SPOTIPY_CACHE: "${{ secrets.SPOTIPY_CACHE}}"
  1. During the workflow you make a request to an endpoint (e.g., Spotify.search). Before making the request, a call to your cache handler's get_cached_token method is made in order to retrieve the token info:
    github.com/plamere/spotipy@c4f6a3fa4b/spotipy/oauth2.py (L507-L513)

  2. Your cache handler's get_cached_token method retrieves the token info from the SPOTIPY_CACHE environment variable.

  3. Suppose the token info is expired. Then, a call to refresh_access_token is made in order to refresh the access token. At the end of this method your cache handler's save_token_to_cache method is called in order to save the new token info to the cache. Crucially, you make a call to self.repository.create_secret in order to update the token info in the repository secret, but the SPOTIPY_CACHE environment variable stays the same.

  4. The next time you make a request to an endpoint, your cache handler's get_cached_token method is called again to retrieve the token info. It returns the expired token info from the SPOTIPY_CACHE environment variable, which was never updated. This means the token info has to be refreshed again.

This process repeats for each call to an endpoint. Your cache handler always returns the now-expired token info from the environment variable.

<!-- gh-comment-id:824217002 --> @Peter-Schorn commented on GitHub (Apr 21, 2021): See my [version](https://github.com/Peter-Schorn/spotipy/blob/secrets-cache-handler/tests/unit/secrets_cache_handler.py) and the [tests](https://github.com/Peter-Schorn/spotipy/blob/secrets-cache-handler/tests/unit/test_secrets_cache_handler.py) for it. Here's what happens in your version: 1. In the setup for the workflow, the token info string is retrieved from the `SPOTIPY_CACHE` secret and stored in the environment variable of the same name, **although these aren't the same things**. This is the only time that this environment variable is set. ``` SPOTIPY_CACHE: "${{ secrets.SPOTIPY_CACHE}}" ``` 2. During the workflow you make a request to an endpoint (e.g., `Spotify.search`). Before making the request, a call to your cache handler's `get_cached_token` method is made in order to retrieve the token info: https://github.com/plamere/spotipy/blob/c4f6a3fa4b4e5e13d78594c33874a0e6cb18361e/spotipy/oauth2.py#L507-L513 3. Your cache handler's `get_cached_token` method retrieves the token info from the `SPOTIPY_CACHE` **environment variable.** 3. Suppose the token info is expired. Then, a call to [`refresh_access_token`](https://github.com/plamere/spotipy/blob/c4f6a3fa4b4e5e13d78594c33874a0e6cb18361e/spotipy/oauth2.py#L552) is made in order to refresh the access token. At the end of this method your cache handler's `save_token_to_cache` method is called in order to save the new token info to the cache. **Crucially**, you make a call to ` self.repository.create_secret` in order to update the token info in the repository secret, **but the** `SPOTIPY_CACHE` **environment variable stays the same.** 4. The next time you make a request to an endpoint, your cache handler's `get_cached_token` method is called again to retrieve the token info. It returns the expired token info from the `SPOTIPY_CACHE ` environment variable, which was never updated. This means the token info has to be refreshed again. This process repeats for each call to an endpoint. Your cache handler always returns the now-expired token info from the environment variable.
Author
Owner

@Peter-Schorn commented on GitHub (Apr 21, 2021):

What you must understand is that the auth manager will not store the token info in memory. get_cached_token will be called before every request is made.

<!-- gh-comment-id:824225024 --> @Peter-Schorn commented on GitHub (Apr 21, 2021): What you must understand is that the auth manager will not store the token info in memory. `get_cached_token` will be called before *every* request is made.
Author
Owner

@stephanebruckert commented on GitHub (Jul 9, 2024):

Closing as a bunch of options were provided

<!-- gh-comment-id:2218391625 --> @stephanebruckert commented on GitHub (Jul 9, 2024): Closing as a bunch of options were provided
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/spotipy#399
No description provided.