[Python] Python Google Calendar script on Raspberry Pi requires periodic re-authentication...

Discussão em 'Python' iniciado por Stack, Abril 21, 2025.

  1. Stack

    Stack Membro Participativo

    I have a Python script running on a Raspberry Pi that fetches Google Calendar events (using google-api-python-client and google-auth-oauthlib) and displays them on a Waveshare e-Paper display. The script (calendarA2.py) runs automatically via cron (@reboot, 06:00, 13:00, 19:00) using a wrapper script that activates a Python virtual environment (venv).

    The Problem:

    The script works fine initially after authenticating manually (running a separate script based on Google's quickstart examples, which generates token.pickle). It successfully uses the refresh token for some time. However, after a period (seems like days or maybe a week), it stops working and requires me to run the full manual authentication process again to regenerate token.pickle. I'm trying to understand why the refresh token stops working or why re-authentication is needed.

    My recent logs (calendar.log attached below) show TimeoutError when trying to fetch events, even immediately after a seemingly successful token refresh logged within the same file run. However, the underlying issue I need to solve is the periodic need to manually re-authenticate from scratch.

    Authentication Logic (inside main function of calendarA2.py):

    This is the code block executed at the start of main() to handle credentials:

    What I've Ruled Out:

    import os
    import pickle
    import logging
    from google.auth.transport.requests import Request
    from google.oauth2.credentials import Credentials
    from googleapiclient.discovery import build
    from google.auth.exceptions import RefreshError

    # SCOPES defined earlier as: ['https://www.googleapis.com/auth/calendar.readonly']

    creds = None
    # Carica token esistente
    if os.path.exists('token.pickle'):
    try:
    with open('token.pickle', 'rb') as token:
    creds = pickle.load(token)
    logging.debug("File token.pickle caricato.")
    except Exception as e:
    logging.error(f"Errore nel caricare token.pickle: {e}. Verrà tentata nuova autenticazione.")
    creds = None

    # Se non valido, prova a refreshare
    if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token: # Check if refresh possible
    try:
    logging.info("Tentativo di refresh del token Google...")
    creds.refresh(Request()) # Perform refresh
    # Salva il token rinfrescato
    with open('token.pickle', 'wb') as token: # Save refreshed
    pickle.dump(creds, token)
    logging.info("Token rinfrescato e salvato.")
    except RefreshError as e: # Handle specific refresh failure
    logging.error(f"Errore durante il refresh del token: {e}")
    logging.error("Impossibile rinfrescare il token. Eseguire 'autentica.py' separatamente.")
    # In a real scenario, the script would exit here (e.g., return from main)
    # For Stack Overflow: Indicate script failure here
    print("SCRIPT EXITING DUE TO REFRESH ERROR")
    exit() # Simulate exit for clarity
    except Exception as e: # Handle other refresh/save errors
    logging.error(f"Errore generico durante il refresh: {e}", exc_info=True)
    # In a real scenario, the script would exit here
    print("SCRIPT EXITING DUE TO GENERIC REFRESH ERROR")
    exit() # Simulate exit for clarity
    else: # Handle cases where refresh is not possible
    # This path is taken if token.pickle doesn't exist,
    # is invalid for other reasons, or lacks a refresh token.
    logging.error("File token.pickle non trovato o invalido senza refresh token.")
    logging.error("Eseguire lo script 'autentica.py' separatamente per autenticarsi.")
    # In a real scenario, the script would exit here
    print("SCRIPT EXITING DUE TO MISSING/INVALID TOKEN")
    exit() # Simulate exit for clarity

    # If we reach here, creds should be valid
    # Proceed to build service...
    # service = build('calendar', 'v3', credentials=creds)
    # ... rest of script


    Based on my usage and checks:

    I haven't manually revoked access in my Google Account settings. I haven't changed my Google account password. The script runs multiple times a day, so the token is used frequently (not prolonged inactivity). I only have this one script using these credentials, and I only run the initial auth flow when forced to, so I shouldn't be hitting refresh token limits. No changes have been made to scopes or OAuth consent screen configuration recently. The token.pickle file appears intact on the filesystem between runs. Key Information:

    My application in Google Cloud Console is for personal use only (only my own Google account ever authorizes it). The application status is currently set to "Testing". I have not clicked "Publish app" as I don't intend for others to use it.

    My Question:

    Given that my refresh logic seems standard and I've ruled out common causes for token invalidation, why do I still need to perform the full manual re-authentication periodically? Could the "Testing" status of the app be the primary reason, even for personal use, causing the refresh token to expire or become invalid eventually (e.g., after 7 days), forcing re-authentication, despite the immediate failures sometimes showing as TimeoutError in the logs? What's the recommended approach for long-term stable authentication for a personal-use script like this?

    Environment:

    Raspberry Pi (running Raspberry Pi OS / Debian based) Python 3.11 (via venv) Libraries: google-api-python-client, google-auth-oauthlib, google-auth-httplib2, requests, pillow, waveshare-epd Scope: https://www.googleapis.com/auth/calendar.readonly Thanks for any insights!

    Continue reading...

Compartilhe esta Página