Skip to content

Commit 576b1b0

Browse files
committed
Added email output
1 parent e58d601 commit 576b1b0

File tree

1 file changed

+126
-16
lines changed

1 file changed

+126
-16
lines changed

app.py

Lines changed: 126 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import os
22
import json
33
import logging
4+
import csv
5+
import smtplib
6+
from email.mime.text import MIMEText
7+
from email.mime.multipart import MIMEMultipart
8+
from email.mime.base import MIMEBase
9+
from email import encoders
410
from typing import Dict, Any, List, Optional, Tuple
11+
from datetime import datetime
512
from sqlalchemy import create_engine, text
613
from sqlalchemy.engine import Connection
714
import requests
@@ -62,21 +69,6 @@ def fetch_data(conn: Connection, sql_limit: int, eval_function_name: str, grade_
6269
# Combine clauses with AND
6370
where_sql = " AND ".join(where_clauses)
6471

65-
# Start with mandatory filters
66-
where_clauses = ["EF.name = :name_param"]
67-
params = {
68-
"name_param": eval_function_name,
69-
"limit_param": limit
70-
}
71-
72-
# Conditionally add the gradeParams filter
73-
if grade_params_json:
74-
where_clauses.append("RA.\"gradeParams\"::jsonb = (:params_param)::jsonb")
75-
params["params_param"] = grade_params_json
76-
77-
# Combine clauses with AND
78-
where_sql = " AND ".join(where_clauses)
79-
8072
sql_query_template = f"""
8173
SELECT
8274
S.submission, S.answer, S.grade, RA."gradeParams"::json as grade_params
@@ -139,7 +131,6 @@ def _execute_request(endpoint_path: str, payload: Dict[str, Any]) -> Tuple[
139131
timeout=10,
140132
)
141133

142-
143134
if response.status_code != 200:
144135
return None, {
145136
"error_type": "HTTP Error",
@@ -242,6 +233,114 @@ def test_endpoint(base_endpoint: str, data_records: List[Dict[str, Any]]) -> Dic
242233
}
243234

244235

236+
def write_errors_to_csv(errors: List[Dict[str, Any]], filename: str) -> Optional[str]:
237+
"""Write error list to CSV file."""
238+
if not errors:
239+
logger.info("No errors to write to CSV.")
240+
return None
241+
242+
try:
243+
filepath = f"/tmp/{filename}"
244+
245+
# Get all unique keys from all error dictionaries
246+
fieldnames = set()
247+
for error in errors:
248+
fieldnames.update(error.keys())
249+
fieldnames = sorted(list(fieldnames))
250+
251+
with open(filepath, 'w', newline='', encoding='utf-8') as f:
252+
writer = csv.DictWriter(f, fieldnames=fieldnames)
253+
writer.writeheader()
254+
writer.writerows(errors)
255+
256+
logger.info(f"CSV file created: {filepath}")
257+
return filepath
258+
259+
except Exception as e:
260+
logger.error(f"Failed to create CSV: {e}")
261+
return None
262+
263+
264+
def send_email_with_results(results: Dict[str, Any], csv_path: Optional[str],
265+
endpoint: str, eval_function_name: str, recipient_email: str):
266+
"""Send email with test results and CSV attachment."""
267+
268+
# Get email config from environment variables
269+
sender_email = os.environ.get('SENDER_EMAIL')
270+
sender_password = os.environ.get('SENDER_PASSWORD')
271+
272+
if not all([sender_email, sender_password, recipient_email]):
273+
logger.warning("Email credentials not configured. Skipping email notification.")
274+
return
275+
276+
try:
277+
# Calculate pass rate
278+
pass_count = results['pass_count']
279+
total_count = results['total_count']
280+
pass_rate = (pass_count / total_count * 100) if total_count > 0 else 0
281+
282+
# Determine status
283+
status = "✓ PASSED" if results['number_of_errors'] == 0 else "✗ FAILED"
284+
285+
# Create email
286+
msg = MIMEMultipart()
287+
msg['From'] = sender_email
288+
msg['To'] = recipient_email
289+
msg['Subject'] = f"Endpoint Test Results - {status} - {eval_function_name}"
290+
291+
# Email body
292+
body = f"""
293+
Evaluation Function Testing Report
294+
=======================
295+
296+
Test Completed: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
297+
Endpoint: {endpoint}
298+
Evaluation Function: {eval_function_name}
299+
300+
Results Summary:
301+
----------------
302+
Status: {status}
303+
Pass Rate: {pass_rate:.1f}% ({pass_count}/{total_count})
304+
Total Tests: {total_count}
305+
Passed: {pass_count}
306+
Failed: {results['number_of_errors']}
307+
308+
{f"⚠ Warning: Testing stopped early after reaching {MAX_ERROR_THRESHOLD} errors." if results['number_of_errors'] >= MAX_ERROR_THRESHOLD else ""}
309+
310+
{'Detailed error information is attached in the CSV file.' if csv_path else 'No errors encountered - all tests passed!'}
311+
312+
This is an automated notification from the endpoint testing system.
313+
"""
314+
315+
msg.attach(MIMEText(body, 'plain'))
316+
317+
# Attach CSV if it exists
318+
if csv_path and os.path.exists(csv_path):
319+
with open(csv_path, 'rb') as f:
320+
part = MIMEBase('application', 'octet-stream')
321+
part.set_payload(f.read())
322+
323+
encoders.encode_base64(part)
324+
part.add_header(
325+
'Content-Disposition',
326+
f'attachment; filename={os.path.basename(csv_path)}'
327+
)
328+
msg.attach(part)
329+
logger.info(f"Attached CSV file: {csv_path}")
330+
331+
# Send email (Gmail SMTP)
332+
server = smtplib.SMTP('smtp.gmail.com', 587)
333+
server.starttls()
334+
server.login(sender_email, sender_password)
335+
server.send_message(msg)
336+
server.quit()
337+
338+
logger.info(f"Email sent successfully to {recipient_email}")
339+
340+
except Exception as e:
341+
logger.error(f"Failed to send email: {e}")
342+
343+
245344
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
246345
"""Main Lambda function entry point."""
247346
conn = None
@@ -256,6 +355,7 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
256355

257356
eval_function_name = payload.get('eval_function_name')
258357
grade_params_json = payload.get('grade_params_json')
358+
recipient_email = payload.get('recipient_email')
259359

260360
if not endpoint_to_test or not eval_function_name:
261361
missing_fields = []
@@ -269,6 +369,16 @@ def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
269369

270370
results = test_endpoint(endpoint_to_test, data_for_test)
271371

372+
# Write errors to CSV and send email
373+
csv_path = None
374+
if results['list_of_errors']:
375+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
376+
csv_filename = f"endpoint_test_errors_{eval_function_name}_{timestamp}.csv"
377+
csv_path = write_errors_to_csv(results['list_of_errors'], csv_filename)
378+
379+
# Send email notification with results
380+
send_email_with_results(results, csv_path, endpoint_to_test, eval_function_name)
381+
272382
return {
273383
"statusCode": 200,
274384
"body": json.dumps({

0 commit comments

Comments
 (0)