47d5af90d317bffde6f0843beabd2d72c88fbfce lrnassar Tue Mar 10 08:18:06 2026 -0700 Adding retry logic around the gmail API call, because it fails periodically (but then works next time it runs). Refs #36801 diff --git src/utils/qa/mlqAutomate.py src/utils/qa/mlqAutomate.py index 748fee08c08..30d93b7f1bd 100755 --- src/utils/qa/mlqAutomate.py +++ src/utils/qa/mlqAutomate.py @@ -14,30 +14,31 @@ import html as html_module import time from datetime import datetime, timedelta from difflib import SequenceMatcher import email from email import policy from email.utils import parseaddr from email.mime.text import MIMEText from functools import wraps import pytz import requests from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from googleapiclient.discovery import build +from googleapiclient.errors import HttpError import anthropic # Configuration CONFIG = { 'REDMINE_URL': 'https://redmine.gi.ucsc.edu', 'REDMINE_API_KEY': '', 'REDMINE_PROJECT': 'maillists', 'CALENDAR_ID': 'ucsc.edu_anbl4254jlssgo3gc2l5c8un5c@group.calendar.google.com', 'CLAUDE_API_KEY': '', # Mailing lists 'MODERATED_LISTS': ['genome@soe.ucsc.edu', 'genome-mirror@soe.ucsc.edu'], 'UNMODERATED_LISTS': ['genome-www@soe.ucsc.edu'], # Name mapping from calendar to Redmine @@ -459,30 +460,31 @@ else: content = msg.get_payload() if content_type == 'text/plain': body = content elif content_type == 'text/html': html_body = content # If no text/plain, convert HTML to text if not body and html_body: body = html_to_text(html_body) return subject, sender, body +@retry(max_attempts=3, delay=2, exceptions=(HttpError,)) def get_pending_moderation_emails(group_name): """Get pending moderation notification emails for a group.""" creds = get_google_credentials() service = build('gmail', 'v1', credentials=creds, cache_discovery=False) # Search for pending moderation emails for this group (exclude trash) query = f'subject:"{group_name} - soe.ucsc.edu admins: Message Pending" -in:trash' results = service.users().messages().list(userId='me', q=query, maxResults=50).execute() pending = [] for msg_ref in results.get('messages', []): msg = service.users().messages().get(userId='me', id=msg_ref['id'], format='full').execute() headers = {h['name']: h['value'] for h in msg['payload']['headers']} # Extract the approval address from the From header