Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I programmatically feed data into a python 'input()' prompt?

I'm using a python wrapper for the Spotify API.

https://spotipy.readthedocs.io/en/latest/#installation

As part of the authorization process, the Spotify API has the user login to Spotify in their default web browser, then sends them to a predefined (when you register the app with Spotify) REDIRECT_URI. This REDIRECT_URI is currently set to localhost:6969. Once you login, it spawns an input() (Input of Death) for you to copy paste the URI that you were redirected to into the command prompt.

I really don't want to sell random people on using a command prompt.

The goal is to open a flask server on localhost:6969 (sue me) and then double tap its /authorize page (send to oauth2 login then capture code)

http://flask.pocoo.org/docs/1.0/

So -

I keep my Flask rig and spotify catcher in jflask.py:

from flask import Flask, request, redirect, session
from requests_oauth2 import OAuth2
from spotipy import Spotify
import spotipy.util as util
import requests
import datetime

app = Flask(__name__)

spotify_auth = OAuth2(
  client_id='e33c6fa0d6a249ccafa232a9cf62a616',
  client_secret='a43b0b6a4de14b97b4e468459e0f7824',
  redirect_uri="http://localhost:6969/authorize",
  site="https://accounts.spotify.com",
  authorization_url="/authorize",
  token_url="/api/token",
  scope_sep=" "
)

# and then use this url as a link within a public login view
print(spotify_auth.authorize_url(
  scope=["user-read-recently-played", "user-top-read"],
  response_type="code"
  )
)
# and have an authorize route that can direct you to Spotify or handle you 
#being redirected back here from Spotify
@app .route('/authorize')
def authorize():

  print('triggered')

  error = request.args.get("error")
  if error:
    abort(404)

  code = request.args.get("code")
  if code:
    data = spotify_auth.get_token(
      code=code,
      grant_type="authorization_code",
    )
    access_token = data["access_token"]
    refresh_token = data["refresh_token"]
    scopes = data["scope"].split()
    self.sp = spotipy.Spotify(auth=access_token)
    print(access_token)
    return self.sp
  else:
    print('code aint defined hoe')
    return redirect(spotify_auth.authorize_url(
      scope=["user-read-recently-played", "user-top-read"],
      response_type="code",
      #state=session['state']
    ))

if __name__ == '__main__':
  app.run(host='localhost', port=6969)

I spawn it with master.py:

from subprocess import Popen, PIPE
import webbrowser

jflask=Popen(['python', 'jflask.py'])

webbrowser.open('http://localhost:6969/authorize')

I've already tried using Popen with stdout=PIPE. After further research, I'm pretty sure that was also inhibiting my success. The specification is meant to be used with subprocess.communicate()

https://docs.python.org/3/library/subprocess.html#subprocess.PIP

Thank you everyone for your help.

like image 566
Gaun Avatar asked Aug 16 '26 16:08

Gaun


2 Answers

Spotipy's prompt_for_user_token method is provided as a quick and dirty method to allow someone using the module locally to get up and running and isn't intended to be code that should be built upon directly if you want arbitrary users to be able to authenticate themselves through a website. Since you have a Flask app you should probably have it and your Spotipy code interact directly through importing rather than using pipes and standard i/o.

Some things to look into are:

  • Flask-Login if you want to manage Users logging in to a website
  • The alternative methods of passing auth to spotipy.Spotify other than just access token (client credentials manager or requests session). These other methods allow the Spotipy Client to refresh an access token if it expires mid-use, handle user revokes, etc.
  • requests-oauth2 (note that this is only one of many OAuth2 Python libs and may not be the best one) which makes all the authorization stuff a bit more convenient and allows you to do something like:
# import Flask things, requests-oauth2, 
from requests_oauth2 import OAuth2
from spotipy import Spotify
from flask import redirect

spotify_auth = OAuth2(
    client_id=os.environ["SPOTIFY_CLIENT_ID"],
    client_secret=os.environ["SPOTIFY_CLIENT_SECRET"],
    redirect_uri=f"http://{os.environ['HOST_NAME']}/authorize",
    site="https://accounts.spotify.com",
    authorization_url="/authorize",
    token_url="/api/token",
    scope_sep=" "
)

# and then use this url as a link within a public login view
spotify_auth.authorize_url(
    scope=["user-read-recently-played", "user-top-read"],
    response_type="code"
)

# and have an authorize route that can direct you to Spotify or handle you being redirected back here from Spotify
@app.route('/authorize')
def authorize():

    error = request.args.get("error")
    if error:
        abort(404)

    code = request.args.get("code")
    if code:
        data = spotify_auth.get_token(
            code=code,
            grant_type="authorization_code",
        )
        access_token = data["access_token"]
        refresh_token = data["refresh_token"]
        scopes = data["scope"].split()
        spotify_client = spotipy.Spotify(auth=access_token)
    else:
        return redirect(spotify_auth.authorize_url(
            scope=["user-read-recently-played", "user-top-read"],
            response_type="code",
            state=session['state']
        ))

On the first visit to /authorize, it will redirect the user to Spotify's Login Page. But when the user logs in successfully there, it redirects the user back to the Flask site (back to /authorize), rather than doing the copy/paste part of prompt_for_user_token().

Once they are back on /authorize, this time there is a code request parameter - so it makes the OAuth2 Client-to-Provider request to transform this code into access tokens for you.

Both this and prompt_for_user_token follow the same approach:

  1. Get the Spotify authorization URL to direct the user to login at
  2. Handle the redirect from Spotify Login - Flask does this by receiving input from the URL that Spotify redirects to, and prompt_for_user_token does this by having you copy/paste the URL from a webpage that doesn't exist.
  3. Make the call to convert the code into access and refresh tokens for a user.

I may have butchered this code a bit because it's cherry-picked from my old Flask implementation of my Spotify integration. Here's a more complete gist but I'm pretty sure a lot of the SQLAlchemy stuff, the authorize view, is quite bad, so take it with a pinch of salt.

like image 62
Rach Sharp Avatar answered Aug 18 '26 07:08

Rach Sharp


You can try modifying the util module. Replace the try-except block with response = raw_input() inside it with:

response = sys.stdin.readline().strip()

Do not forget to import sys if it is not already imported.

This should allow normal PIPE-ing.

Or you can use pexpect library instead of the subprocess module. It knows how to handle input which tweaked fcntl flags of a terminal or uses mscvrt on Windows.

Also, when PIPE-ing data, do not forget that either raw_input(), input() or sys.stdin.readline() will not return until they receive the appropriate end of line character. "\r", "\n" or "\r\n". Did you send it before with your authorization URL?

like image 27
Dalen Avatar answered Aug 18 '26 06:08

Dalen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!