1

When I run the app locally (at localhost:8080), it works as it should. When deployed on heroku, it often gives "NameError: 'APIservice' is not defined".

APIservice is the variable containing the google API object, per Google's documentation. It is build in the 'start' view:

@app.route('/start')
def start_teatime(): # Checks auth & builds calendar API (APIservice)
    print('stat_teatime trigger')

    global APIservice

    if 'credentials' not in session:
        return redirect('authorize')

  # Load credentials from the session.
    credentials = google.oauth2.credentials.Credentials(
        **session['credentials'])

    APIservice = googleapiclient.discovery.build(
        API_SERVICE_NAME, API_VERSION, credentials=credentials)


    return redirect(url_for('user_input'))

APIservice gets used in the next view ('user_input') and in different functions in the same .py file. Yet, Heroku says APIservice is not defined. Does anyone have an idea why this is happening?

  • Does this answer your question? [Are global variables thread safe in flask? How do I share data between requests?](https://stackoverflow.com/questions/32815451/are-global-variables-thread-safe-in-flask-how-do-i-share-data-between-requests) – ChrisGPT was on strike Jul 25 '20 at 16:18
  • Is there another route that attempts to use `APIservice`, assuming that someone hit `/start` first? – Dave W. Smith Jul 25 '20 at 16:23
  • @Chris I think this is where I am a little out of my debt. My web app uses multiple global variables, which only need to store stuff during a session. The general flow is: users clicks start>user gives some input>script does some processing (using google APIs)>web api notifies user that it's done. What would you recommend as a way to handle these global variables? Should I put them all into a Flask session? – Dailyfriend Jul 25 '20 at 17:36
  • @DaveW.Smith There is one route which always starts with `/start`. `APIservice` is used multiple times along the route, but all after `/start` – Dailyfriend Jul 25 '20 at 17:38
  • Can you link the google docs which you are trying to follow for this? – v25 Jul 25 '20 at 18:00
  • @v25 https://developers.google.com/identity/protocols/oauth2/web-server Specifically the Complete Example at the bottom. They do mention: `# In a production app, you likely want to save these credentials in a persistent database instead.` but I don't necessarily want that. My users will use the app once a week, so it's fine for them to log in again at that time. – Dailyfriend Jul 25 '20 at 18:16

1 Answers1

1

You're probably seeing this behaviour because Heroku defaults to two gunicorn workers. This is why global variables are not good for this type of app. The request that comes in is handled by a random worker, thus your error.

A quick workaround for the problem in your sample code may be to implement a function (at the global level) which returns the APIService:

def get_api_service(creds):
    
    credentials = google.oauth2.credentials.Credentials(
        **creds)

    # Save credentials back to session in case access token was refreshed.
    session['credentials'] = credentials_to_dict(credentials)

    APIservice = googleapiclient.discovery.build(
        API_SERVICE_NAME, API_VERSION, credentials=credentials)
    
    return APIService

Then in your route call this like:

    # ...    

    # Load credentials from the session.
    if 'credentials' not in session:
        return redirect('authorize')

    APIservice = get_api_service(session['credentials'])

    # Do something with APIservice

    # ...

Your other routes would then need to repeat this code, and obtain APIservice in the same manner.

The only thing I'm unsure of here is if it's recommended to call what's now in the get_api_service function on every request. The documentation only provides one example /test route and not any others, which hints you would repeat this in every route that needs to obtain the APIservice object.

v25
  • 7,096
  • 2
  • 20
  • 36
  • Thank you! I like the idea of having a function which returns the `APIservice` object, that would solve this NameError. I'm afraid the same thing will happen for other global variables though. For example, the script reads all events in the next 7 days and saves their details to global variables: `appointmentTime[]`, `endTime[]`, `location[]`, etc. If I understand it correctly, there global variables won't work with 2 gunicorn workers. Would storing these global variables in the Flask session be a good solution? Something like `session['appointmentTime'] = [*list of times*]`? – Dailyfriend Jul 26 '20 at 08:32
  • Adding comment for anyone how runs into a similar issue: I ended up creating 3 SQL databases with SQLalchemy which now contain all previously global variables. This made my webapp a lot more flexible. It was a bit daunting at first, but I am very glad I went with databases. – Dailyfriend Jan 10 '21 at 21:10