"""
ACORN Client
Handles authentication and data retrieval from the University of Toronto's ACORN system.
Uses web scraping since ACORN doesn't provide a public API.
Note: This is a template implementation. Actual implementation would require
proper web scraping with requests/selenium and parsing with BeautifulSoup.
"""
import requests
from typing import List, Dict, Any
from datetime import datetime
class AcornClient:
"""Client for interacting with UofT ACORN system"""
def __init__(self, username: str, password: str):
"""
Initialize ACORN client with credentials.
Args:
username: UTORid username
password: ACORN password
"""
self.username = username
self.password = password
self.session = requests.Session()
self.base_url = "https://acorn.utoronto.ca"
self.authenticated = False
def login(self) -> bool:
"""
Authenticate with ACORN.
Returns:
True if authentication successful, False otherwise
Note: Actual implementation would handle:
- SAML authentication
- Two-factor authentication
- Session management
- Cookie handling
"""
# TODO: Implement actual ACORN login
# This is a placeholder that simulates successful login
print(f"🔐 Logging in to ACORN as {self.username}...")
try:
# In real implementation:
# 1. Navigate to ACORN login page
# 2. Handle SAML redirect
# 3. Submit credentials
# 4. Handle 2FA if enabled
# 5. Verify successful login
self.authenticated = True
print("✅ Successfully logged in to ACORN")
return True
except Exception as e:
print(f"❌ ACORN login failed: {str(e)}")
self.authenticated = False
return False
def get_enrolled_courses(self) -> List[Dict[str, Any]]:
"""
Retrieve all courses the student is currently enrolled in.
Returns:
List of course dictionaries
Note: Actual implementation would:
- Navigate to enrollment page
- Parse course listings
- Extract course details
- Handle pagination if needed
"""
if not self.authenticated:
self.login()
# TODO: Implement actual course retrieval
# This returns mock data for demonstration
mock_courses = [
{
"course_code": "CSC148H1",
"course_name": "Introduction to Computer Science",
"section": "LEC0101",
"credits": 0.5,
"term": "Fall 2025",
"status": "Enrolled"
},
{
"course_code": "MAT137Y1",
"course_name": "Calculus with Proofs",
"section": "LEC0101",
"credits": 1.0,
"term": "Fall 2025 - Winter 2026",
"status": "Enrolled"
},
{
"course_code": "PHY151H1",
"course_name": "Foundations of Physics I",
"section": "LEC0101",
"credits": 0.5,
"term": "Fall 2025",
"status": "Enrolled"
},
{
"course_code": "CSC165H1",
"course_name": "Mathematical Expression and Reasoning for CS",
"section": "LEC0101",
"credits": 0.5,
"term": "Fall 2025",
"status": "Enrolled"
},
{
"course_code": "ENG100H1",
"course_name": "Effective Writing",
"section": "LEC0101",
"credits": 0.5,
"term": "Fall 2025",
"status": "Enrolled"
}
]
return mock_courses
def get_course_details(self, course_code: str) -> Dict[str, Any]:
"""
Get detailed information about a specific course.
Args:
course_code: Course code (e.g., "CSC148H1")
Returns:
Dictionary with detailed course information
Note: Actual implementation would:
- Search for course in ACORN
- Navigate to course detail page
- Parse all course information
- Extract prerequisites, exclusions, etc.
"""
if not self.authenticated:
self.login()
# TODO: Implement actual course detail retrieval
# This returns mock data for demonstration
mock_details = {
"CSC148H1": {
"course_code": "CSC148H1",
"course_name": "Introduction to Computer Science",
"description": "Abstract data types and data structures for implementing them. Linked lists, stacks, queues, trees. Specifications and analysis of algorithms. Sorting and searching. Design and analysis of recursive algorithms.",
"prerequisites": ["CSC108H1"],
"corequisites": [],
"exclusions": ["CSC150H1"],
"instructor": "Dr. Jane Smith",
"meeting_times": [
{"day": "Monday", "time": "10:00-11:00", "type": "Lecture"},
{"day": "Wednesday", "time": "10:00-11:00", "type": "Lecture"},
{"day": "Friday", "time": "14:00-16:00", "type": "Tutorial"}
],
"location": "BA1170",
"delivery_mode": "In-person",
"breadth_requirement": "The Physical and Mathematical Universes (5)",
"distribution": "Science"
},
"MAT137Y1": {
"course_code": "MAT137Y1",
"course_name": "Calculus with Proofs",
"description": "A conceptual approach to calculus with an emphasis on proofs and understanding. Topics include limits, continuity, differentiation, integration, and sequences and series.",
"prerequisites": ["High school calculus"],
"corequisites": [],
"exclusions": ["MAT135H1", "MAT136H1", "MAT157Y1"],
"instructor": "Dr. John Doe",
"meeting_times": [
{"day": "Tuesday", "time": "11:00-12:00", "type": "Lecture"},
{"day": "Thursday", "time": "11:00-12:00", "type": "Lecture"}
],
"location": "MP202",
"delivery_mode": "In-person",
"breadth_requirement": "The Physical and Mathematical Universes (5)",
"distribution": "Science"
}
}
return mock_details.get(course_code, {
"error": f"Course {course_code} not found",
"course_code": course_code
})
def get_schedule(self) -> Dict[str, Any]:
"""
Get the student's weekly class schedule.
Returns:
Dictionary with schedule organized by day
Note: Actual implementation would:
- Navigate to timetable page
- Parse schedule grid
- Extract all class times and locations
- Organize by day of week
"""
if not self.authenticated:
self.login()
# TODO: Implement actual schedule retrieval
# This returns mock data for demonstration
mock_schedule = {
"schedule": {
"monday": [
{
"course_code": "CSC148H1",
"course_name": "Intro to CS",
"time": "10:00-11:00",
"location": "BA1170",
"type": "Lecture"
},
{
"course_code": "PHY151H1",
"course_name": "Foundations of Physics I",
"time": "13:00-14:00",
"location": "MP202",
"type": "Lecture"
}
],
"tuesday": [
{
"course_code": "MAT137Y1",
"course_name": "Calculus with Proofs",
"time": "11:00-12:00",
"location": "MP202",
"type": "Lecture"
},
{
"course_code": "CSC165H1",
"course_name": "Math Expression for CS",
"time": "15:00-16:00",
"location": "BA1170",
"type": "Lecture"
}
],
"wednesday": [
{
"course_code": "CSC148H1",
"course_name": "Intro to CS",
"time": "10:00-11:00",
"location": "BA1170",
"type": "Lecture"
},
{
"course_code": "ENG100H1",
"course_name": "Effective Writing",
"time": "14:00-15:00",
"location": "SS1071",
"type": "Seminar"
}
],
"thursday": [
{
"course_code": "MAT137Y1",
"course_name": "Calculus with Proofs",
"time": "11:00-12:00",
"location": "MP202",
"type": "Lecture"
},
{
"course_code": "PHY151H1",
"course_name": "Foundations of Physics I",
"time": "16:00-18:00",
"location": "MP126",
"type": "Lab"
}
],
"friday": [
{
"course_code": "CSC148H1",
"course_name": "Intro to CS",
"time": "14:00-16:00",
"location": "BA3116",
"type": "Tutorial"
}
],
"saturday": [],
"sunday": []
},
"total_hours_per_week": 15
}
return mock_schedule
def logout(self):
"""Log out from ACORN and clear session"""
self.session.close()
self.authenticated = False
print("👋 Logged out from ACORN")