# Evaluation Endpoint Notifier # Handles POST requests to evaluation endpoints with retry logic import requests import logging import time import json from typing import Dict, Any from datetime import datetime from .config import Config logger = logging.getLogger(__name__) class EvaluationNotifier: """ Handles notification of evaluation endpoints with retry logic """ def __init__(self): self.max_retries = Config.RETRY_ATTEMPTS self.timeout = 30 # 30 second timeout for requests # TODO (Copilot): POST to evaluation_url with: # { # email, task, round, nonce, # repo_url, commit_sha, pages_url # } # Headers: Content-Type: application/json # Retry on failure: exponential backoff (1s, 2s, 4s…) up to 5 times # Log response and success/failure def notify_evaluation_endpoint(self, evaluation_url: str, data: Dict[str, Any]) -> bool: """ Send evaluation data to the endpoint with retry logic Args: evaluation_url: URL to POST the evaluation data to data: Dictionary containing evaluation results Returns: True if notification was successful, False otherwise """ if not evaluation_url: logger.warning("No evaluation URL provided") return False # Add timestamp to data data['timestamp'] = datetime.utcnow().isoformat() logger.info(f"Notifying evaluation endpoint: {evaluation_url}") logger.debug(f"Evaluation data: {json.dumps(data, indent=2)}") for attempt in range(self.max_retries): try: # Calculate backoff delay: 1s, 2s, 4s, 8s, 16s if attempt > 0: delay = 2 ** (attempt - 1) logger.info(f"Retrying in {delay} seconds (attempt {attempt + 1}/{self.max_retries})") time.sleep(delay) # Make the POST request response = requests.post( evaluation_url, json=data, headers={ 'Content-Type': 'application/json', 'User-Agent': 'LLM-Code-Deployment/1.0' }, timeout=self.timeout ) # Log the response logger.info(f"Evaluation endpoint response: {response.status_code}") logger.debug(f"Response body: {response.text}") # Check if successful if response.status_code in [200, 201, 202]: logger.info("Successfully notified evaluation endpoint") return True elif response.status_code in [400, 401, 403, 404]: # Client errors - don't retry logger.error(f"Client error {response.status_code}, not retrying: {response.text}") return False else: # Server errors - retry logger.warning(f"Server error {response.status_code}, will retry: {response.text}") continue except requests.exceptions.Timeout: logger.warning(f"Request timeout on attempt {attempt + 1}") continue except requests.exceptions.ConnectionError as e: logger.warning(f"Connection error on attempt {attempt + 1}: {str(e)}") continue except requests.exceptions.RequestException as e: logger.error(f"Request error on attempt {attempt + 1}: {str(e)}") continue except Exception as e: logger.error(f"Unexpected error on attempt {attempt + 1}: {str(e)}") continue logger.error(f"Failed to notify evaluation endpoint after {self.max_retries} attempts") return False def notify_success( self, evaluation_url: str, email: str, task: str, round_num: int, nonce: str, repo_url: str, commit_sha: str, pages_url: str ) -> bool: """ Notify evaluation endpoint of successful deployment Args: evaluation_url: URL to POST to email: Student email task: Task identifier round_num: Round number (1 or 2) nonce: Request nonce repo_url: GitHub repository URL commit_sha: Git commit SHA pages_url: GitHub Pages URL Returns: True if notification was successful """ data = { 'email': email, 'task': task, 'round': round_num, 'nonce': nonce, 'repo_url': repo_url, 'commit_sha': commit_sha, 'pages_url': pages_url, 'status': 'success' } return self.notify_evaluation_endpoint(evaluation_url, data) def notify_failure( self, evaluation_url: str, email: str, task: str, round_num: int, nonce: str, error: str ) -> bool: """ Notify evaluation endpoint of failed deployment Args: evaluation_url: URL to POST to email: Student email task: Task identifier round_num: Round number (1 or 2) nonce: Request nonce error: Error message Returns: True if notification was successful """ data = { 'email': email, 'task': task, 'round': round_num, 'nonce': nonce, 'status': 'failed', 'error': error } return self.notify_evaluation_endpoint(evaluation_url, data) def test_evaluation_endpoint(self, evaluation_url: str) -> bool: """ Test if an evaluation endpoint is reachable Args: evaluation_url: URL to test Returns: True if endpoint is reachable """ try: # Send a simple GET request to test connectivity response = requests.get( evaluation_url, timeout=10, headers={'User-Agent': 'LLM-Code-Deployment/1.0'} ) logger.info(f"Evaluation endpoint test: {response.status_code}") return True except Exception as e: logger.warning(f"Evaluation endpoint test failed: {str(e)}") return False