An API for retrieving public data from the University of Ottawa and Carleton University.
Project description
Schedulo API
A REST API server for retrieving public data from Canadian universities, including the University of Ottawa and Carleton University.
This package features a completely refactored, clean architecture with improved maintainability and extensibility.
โจ Features
- ๐ซ Multi-University Support: University of Ottawa and Carleton University
- ๐ Complete Course Data: Catalogs, timetables, prerequisites, components
- ๐ Academic Programs: 840+ programs with filtering, search, and bulk export
- โก Live Timetable Data: Real-time course availability and scheduling
- โญ Rate My Professor Integration: Professor ratings for both universities
- ๐ FastAPI REST API: Complete HTTP API with interactive documentation
- ๐ฆ Laravel Integration: Bulk program export for database seeding
- ๐ง Clean Architecture: Layered design with proper separation of concerns
- ๐ Python Library: Programmatic access to services for building your own applications
- ๐ Type Safety: Full type annotations with Pydantic models
๐๏ธ New Architecture
The package has been completely refactored with a clean layered architecture:
uoapi/
โโโ core/ # Domain models & interfaces
โโโ universities/ # University-specific implementations
โโโ services/ # Business logic layer
โโโ server/ # REST API server
โโโ utils/ # Shared utilities
Key Benefits:
- โ Single Responsibility: Each module has one clear purpose
- โ Consistent Models: Unified data structures across universities
- โ Easy Extension: Add new universities by implementing simple interfaces
- โ Better Testing: Clear boundaries enable comprehensive testing
- โ Type Safety: Full type annotations throughout
๐ Quick Start
Installation
# From PyPI (Recommended)
pip install schedulo-api
# From Source
pip install git+https://github.com/Rain6435/uoapi.git@dev
# Development Installation
git clone https://github.com/Rain6435/uoapi.git
cd uoapi
pip install -e .[tests]
Basic Usage
Starting the Server
# Start the REST API server
schedulo-server --port 8000
# With custom options
schedulo-server --host 0.0.0.0 --port 8080 --workers 4
# Development mode with auto-reload
schedulo-server --reload --log-level debug
# Interactive docs available at:
# http://localhost:8000/docs
# http://localhost:8000/redoc
Programmatic Usage
# Start server programmatically
from uoapi.server.app import create_app
import uvicorn
app = create_app()
uvicorn.run(app, host="127.0.0.1", port=8000)
Using the Python Library
from uoapi.core import University
from uoapi.services import DefaultCourseService, DefaultTimetableService
# Initialize services
course_service = DefaultCourseService()
timetable_service = DefaultTimetableService()
# Get all subjects for a university
subjects = course_service.get_subjects(University.CARLETON)
print(f"Found {len(subjects)} subjects")
# Get courses for a specific subject
courses = course_service.get_courses(University.CARLETON, "COMP")
print(f"Found {len(courses)} COMP courses")
# Search courses
search_result = course_service.search_courses(University.UOTTAWA, "programming")
print(f"Found {search_result.total_found} courses matching 'programming'")
# Get live timetable data
if University.CARLETON in timetable_service.get_supported_universities():
live_data = timetable_service.get_live_courses(
University.CARLETON,
term_code="202501",
subjects=["COMP"],
max_courses_per_subject=10
)
print(f"Found {live_data.courses_offered} offered courses")
๐ Complete Usage Guide
University Data Access
Course Service
from uoapi.core import University
from uoapi.services import DefaultCourseService
service = DefaultCourseService()
# Get all supported universities
universities = service.get_all_universities()
# Get subjects
subjects = service.get_subjects(University.UOTTAWA)
for subject in subjects[:5]:
print(f"{subject.code}: {subject.name}")
# Get courses with filtering
courses = service.get_courses(
University.CARLETON,
subject_code="COMP",
query="database"
)
# Get specific course
course = service.get_course_by_code(University.UOTTAWA, "CSI3140")
print(f"{course.title}: {course.credits} credits")
# Get course statistics
stats = service.get_course_statistics(University.CARLETON)
print(f"Total courses: {stats['total_courses']}")
Timetable Service
from uoapi.services import DefaultTimetableService
service = DefaultTimetableService()
# Check which universities support live data
supported = service.get_supported_universities()
print(f"Live data supported by: {[u.value for u in supported]}")
# Get available terms
terms = service.get_available_terms(University.CARLETON)
for code, name in terms:
print(f"{code}: {name}")
# Get live course data
result = service.get_live_courses(
university=University.CARLETON,
term_code="202501",
subjects=["COMP", "MATH"],
max_courses_per_subject=20
)
print(f"Processing time: {result.processing_time:.2f}s")
print(f"Offering rate: {result.offering_rate:.1f}%")
for course in result.courses:
if course.is_offered:
print(f"\n{course.course_code}: {course.title}")
for section in course.sections:
print(f" {section.section}: {section.instructor} - {section.status}")
for mt in section.meeting_times:
print(f" {mt.days} {mt.start_time}-{mt.end_time}")
Rating Service
from uoapi.services import DefaultRatingService
service = DefaultRatingService()
# Get individual instructor rating
rating = service.get_instructor_rating("John Smith", University.UOTTAWA)
if rating:
print(f"Rating: {rating['rating']}/5.0")
print(f"Difficulty: {rating['avg_difficulty']}/5.0")
# Get batch ratings
instructors = [("Jane", "Doe"), ("John", "Smith")]
ratings = service.get_batch_ratings(instructors, University.CARLETON)
# Enhance courses with ratings
enhanced_courses = service.inject_ratings_into_courses(courses, University.UOTTAWA)
REST API Server
The Schedulo API provides a comprehensive FastAPI-based REST server with interactive documentation, structured responses, and powerful filtering capabilities.
Starting the Server
# Start the server (Recommended)
schedulo-server --port 8000
# With additional options
schedulo-server --host 0.0.0.0 --port 8080 --workers 4
# Development mode
schedulo-server --reload --log-level debug
# Or programmatically
python -c "
from uoapi.server.app import create_app
import uvicorn
app = create_app()
uvicorn.run(app, host='127.0.0.1', port=8000)
"
Interactive Documentation: http://localhost:8000/docs ReDoc Documentation: http://localhost:8000/redoc
Core Endpoints
University Information
# List all supported universities
curl http://localhost:8000/universities
# Get university-specific information
curl http://localhost:8000/universities/carleton/info
curl http://localhost:8000/universities/uottawa/info
Subjects
# Get subjects (preview - first 20)
curl http://localhost:8000/universities/carleton/subjects
curl http://localhost:8000/universities/uottawa/subjects
# Get all subjects (complete catalog)
curl http://localhost:8000/universities/carleton/subjects/catalog
curl http://localhost:8000/universities/uottawa/subjects/catalog
Course Catalog (Static Data)
# Get catalog courses by subject
curl "http://localhost:8000/universities/carleton/courses/catalog?subjects=COMP,MATH&limit=10"
curl "http://localhost:8000/universities/uottawa/courses/catalog?subjects=CSI,MAT&limit=5"
# Get all catalog courses (warning: large response)
curl "http://localhost:8000/universities/carleton/courses/catalog"
# Get single course (catalog data only)
curl http://localhost:8000/universities/carleton/courses/COMP1005
curl http://localhost:8000/universities/uottawa/courses/CSI3140
Live Timetable Data
# Get available terms for live data
curl http://localhost:8000/universities/carleton/terms
curl http://localhost:8000/universities/uottawa/terms
# Multiple courses with live sections
curl "http://localhost:8000/universities/carleton/courses/live?term=fall&year=2025&subjects=COMP,MATH&limit=20&include_ratings=true"
curl "http://localhost:8000/universities/uottawa/courses/live?term=winter&year=2025&subjects=CSI,CEG&limit=10"
# Filter by specific course codes
curl "http://localhost:8000/universities/carleton/courses/live?term=fall&year=2025&subjects=COMP&course_codes=COMP1005,COMP1405"
# Single course with structured sections
curl "http://localhost:8000/universities/carleton/courses/COMP1005/live?term=fall&year=2025&include_ratings=true"
New Single Course Response Structure:
{
"university": "carleton",
"term_code": "202530",
"term_name": "Fall 2025",
"course": {
"course_code": "COMP1005",
"subject_code": "COMP",
"title": "Programming Concepts",
"credits": 0.5,
"is_offered": true,
"sections_found": 13
},
"sections": [
{
"section": "A",
"components": [
{
"name": "A",
"crn": "31108",
"status": "Open",
"credits": 0.5,
"schedule_type": "Lecture",
"instructor": "Ava McKenney",
"meeting_times": [
{
"start_date": "Sep 03, 2025",
"end_date": "Dec 05, 2025",
"days": "Wed Fri",
"start_time": "13:05",
"end_time": "14:25"
}
],
"notes": ["Also Register in: COMP 1005 A1 or A2 or A3"],
"rmp_rating": {
"instructor": "Ava McKenney",
"rating": 4.2,
"num_ratings": 15
}
},
{
"name": "A1",
"crn": "31109",
"status": "Open",
"schedule_type": "Tutorial",
"instructor": "Ava McKenney"
},
{
"name": "A2",
"crn": "31110",
"status": "Full, No Waitlist",
"schedule_type": "Tutorial"
}
]
}
]
}
Professor Ratings
# Get Rate My Professor ratings
curl "http://localhost:8000/universities/carleton/professors/John/Smith"
curl "http://localhost:8000/universities/uottawa/professors/Lucia/Moura"
๐ Academic Programs
# Get all programs for a university
curl "http://localhost:8000/universities/carleton/programs?limit=10"
curl "http://localhost:8000/universities/uottawa/programs?limit=10"
# Filter programs by criteria
curl "http://localhost:8000/universities/carleton/programs?faculty=engineering&limit=5"
curl "http://localhost:8000/universities/uottawa/programs?degree_type=bachelor&faculty=science"
# Search programs by name
curl "http://localhost:8000/universities/carleton/programs/search?q=computer&limit=5"
curl "http://localhost:8000/universities/uottawa/programs/search?q=engineering&limit=5"
# Get available filter options
curl "http://localhost:8000/universities/carleton/programs/filters"
curl "http://localhost:8000/universities/uottawa/programs/filters"
# ๐ BULK EXPORT - All programs for Laravel/database import
curl "http://localhost:8000/universities/carleton/programs/export"
curl "http://localhost:8000/universities/uottawa/programs/export"
Programs Data Coverage:
- ๐ Carleton University: 129 programs across 5 faculties
- ๐ University of Ottawa: 700+ programs across 9 faculties
- ๐ Total: 840+ academic programs available
Bulk Export Features:
- ๐ฆ One-shot export: Complete university + faculty + program data
- ๐๏ธ Laravel-compatible: Ready for direct database import
- ๐ Relational structure: Proper university โ faculty โ program hierarchy
- ๐ Rich metadata: Export timestamps, counts, and import notes
API Features
- ๐๏ธ Structured Responses: Properly grouped course sections and components
- ๐ Academic Programs: Complete program catalog with search and filtering
- ๐ฆ Bulk Export: Laravel-ready program data with relational structure
- โญ Professor Integration: Optional Rate My Professor ratings via
?include_ratings=true - ๐ Smart Filtering: Filter by subjects, course codes, terms, faculties, disciplines
- ๐ University-Specific: Handles different term formats and subject code lengths
- ๐ Comprehensive Data: Course catalogs, live timetables, prerequisites, programs
- ๐ High Performance: Direct single-course queries bypass bulk discovery
- ๐ Interactive Docs: Auto-generated OpenAPI documentation
- ๐ก๏ธ Type Safety: Full Pydantic validation and serialization
- ๐ฏ RESTful Design: Clean, predictable endpoint structure
๐ง Advanced Usage
Custom University Provider
from uoapi.core import UniversityProvider, University, Subject, Course
from uoapi.universities import BaseUniversityProvider
class MyUniversityProvider(BaseUniversityProvider):
@property
def university(self) -> University:
return University.MYUNI # Add to enum first
@property
def name(self) -> str:
return "My University"
def get_subjects(self) -> List[Subject]:
# Implement subject scraping/loading
return []
def get_courses(self, subject_code: str = None) -> List[Course]:
# Implement course scraping/loading
return []
# Register with service
from uoapi.services import DefaultCourseService
service = DefaultCourseService()
service._providers[University.MYUNI] = MyUniversityProvider()
Custom API Endpoints
from fastapi import APIRouter
from uoapi.server.app import create_app
# Create custom router
custom_router = APIRouter()
@custom_router.get("/custom/endpoint")
async def custom_endpoint():
return {"message": "Custom functionality"}
# Add to app
app = create_app()
app.include_router(custom_router, prefix="/api/v1", tags=["custom"])
Configuration
from uoapi.utils import get_config
config = get_config()
# Adjust cache settings
config.cache.ttl_seconds = 7200 # 2 hours
config.cache.uottawa_ttl = 14400 # 4 hours for UOttawa
# Adjust scraping settings
config.scraping.timeout_seconds = 60
config.scraping.concurrent_workers = 8
# API settings
config.api.port = 9000
config.api.debug = True
๐ Data Models
Unified Course Model
from uoapi.core import Course
# All universities use the same model
course = Course(
course_code="COMP1001",
subject_code="COMP",
course_number="1001",
title="Introduction to Computing",
description="Basic computing concepts...",
credits=3,
university=University.CARLETON,
components=["Lecture", "Laboratory"],
prerequisites="None",
sections=[...], # Live sections if available
is_offered=True
)
Search Results
from uoapi.core import SearchResult
result = SearchResult(
university=University.UOTTAWA,
query="programming",
subject_filter="CSI",
total_found=25,
courses=[...],
metadata={"search_method": "text_search"}
)
Live Course Discovery
from uoapi.core import DiscoveryResult
result = DiscoveryResult(
term_code="202501",
term_name="Winter 2025",
university=University.CARLETON,
subjects_queried=["COMP", "MATH"],
total_courses=150,
courses_offered=142,
offering_rate=94.7,
processing_time=25.3,
courses=[...]
)
๐งช Development
Setup
git clone https://github.com/Rain6435/uoapi.git
cd uoapi
pip install -e .[tests]
Testing
# Run all tests
make test # or pytest
# Test specific components
pytest tests/core/
pytest tests/services/
pytest tests/universities/
# Test with coverage
pytest --cov=uoapi tests/
# Type checking
make check # or mypy src/
# Linting
make lint # or flake8
# All checks
make # test + lint + typecheck
Code Quality
The refactored codebase maintains high code quality with:
- 100% type coverage with mypy
- Comprehensive tests for all components
- Consistent formatting with black
- Clean imports and modular design
- Documentation for all public APIs
๐ Migration Guide
From Old API
# Old way
from uoapi.course.course_info import scrape_subjects, get_courses
subjects = scrape_subjects()
courses = list(get_courses(subjects[0]['link']))
# New way (recommended)
from uoapi.core import University
from uoapi.services import DefaultCourseService
service = DefaultCourseService()
subjects = service.get_subjects(University.UOTTAWA)
courses = service.get_courses(University.UOTTAWA, subjects[0].code)
Legacy Compatibility
# Old imports still work
from uoapi.course import scrape_subjects, get_courses # โ
Still works
from uoapi.carleton.discovery import CarletonDiscovery # โ
Still works
from uoapi.server.app import create_app # โ
Still works
# But new imports are cleaner
from uoapi.core import * # โ
New unified models
from uoapi.services import * # โ
Business logic
from uoapi.interfaces.api import create_app # โ
Clean API
๐ Troubleshooting
Common Issues
- Import errors: Ensure Python 3.10+ and proper installation
- University not supported: Check
service.get_all_universities() - Term validation: Use
timetable_service.get_available_terms()first - Rate limiting: Reduce concurrent workers if getting blocked
- Live data not available: Both Carleton and UOttawa support live timetable data
Debug Mode
import logging
logging.basicConfig(level=logging.DEBUG)
# Or start server with debug logging
schedulo-server --log-level debug
Configuration Issues
from uoapi.utils import get_config, reload_config
# Check current config
config = get_config()
print(config.to_dict())
# Reload with different environment
reload_config("development")
๐ฏ What's New in v4.0+
Breaking Changes
- ๐จ CLI Removed: The package is now server-only. Use
schedulo-serverto start the REST API - ๐ง Server-First Architecture: All functionality now accessed via HTTP API or Python library
Major Enhancements
- ๐ง Enhanced Section Parsing: Complete retrieval of all course sections, lectures, tutorials, and labs
- โก Improved Data Accuracy: Fixed Banner system parsing to capture all available course sections
- ๐ Better Performance: Optimized server with worker support
- ๐จโ๐ซ Professor Ratings: Rate My Professor integration via API endpoints
- ๐ Complete REST API: All functionality available via HTTP endpoints
- ๐ฏ Smart Subject Validation: University-specific subject code validation (4-letter for Carleton, 3-letter for UOttawa)
Architecture Improvements
- ๐๏ธ Clean Architecture: Proper layered design with separation of concerns
- ๐ง Service Layer: Business logic separated from data access
- ๐ฏ Single Responsibility: Each module has one clear purpose
- ๐ Dependency Inversion: High-level modules don't depend on low-level details
Developer Experience
- โ Type Safety: Complete type annotations with Pydantic
- ๐งช Better Testing: Clear boundaries enable comprehensive testing
- ๐ Better Documentation: Comprehensive examples and API docs
- ๐ง Easy Extension: Add new universities via simple interfaces
- ๐ API-First: Build your own clients or integrate with any platform
User Experience
- ๐จ Consistent APIs: Same patterns across all universities
- โก Better Performance: Improved caching and parallel processing
- ๐ Better Error Messages: Structured exceptions with helpful details
- ๐ Richer Data: Enhanced models with metadata and validation
- ๐ Universal Access: Use any HTTP client to consume the API
๐ค Contributing
We welcome contributions! The architecture makes it easy to contribute:
- Add Universities: Implement
UniversityProviderinterface - Add Features: Extend service classes with new functionality
- Add API Endpoints: Create new REST API endpoints
- Fix Bugs: Clear modular structure makes debugging easier
Contribution Process
# 1. Fork and clone
git clone https://github.com/your-username/uoapi.git
# 2. Create feature branch
git checkout -b feature/my-feature
# 3. Make changes and test
make test
make lint
# 4. Submit PR
git push origin feature/my-feature
๐ License
GNU LGPLv3.0 - See the COPYING and COPYING.LESSER files for details.
๐ Acknowledgments
- Original uoapi by Andrew Nagarajah
- University of Ottawa and Carleton University for public data access
- Rate My Professor for their API
- The Python community for excellent libraries and tools
Ready to explore university course data via REST API?
pip install schedulo-api
schedulo-server --port 8000 # Start the server
# Visit http://localhost:8000/docs for interactive documentation
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file schedulo_api-4.0.0.tar.gz.
File metadata
- Download URL: schedulo_api-4.0.0.tar.gz
- Upload date:
- Size: 4.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e07ac2a0c0c3eb6832b4ad3bdb96597561a6d89f55ac6319dd7ce9b465fc6d7
|
|
| MD5 |
82ed2c4ef396db46e0e209e9a8a95c23
|
|
| BLAKE2b-256 |
b860cedcc42c577550c2c4bc3847294f92ea6ab0b8c7097608b1c2e4e903c466
|
File details
Details for the file schedulo_api-4.0.0-py3-none-any.whl.
File metadata
- Download URL: schedulo_api-4.0.0-py3-none-any.whl
- Upload date:
- Size: 1.8 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ee7c45698a851db56090d04c483401053dc0031e32ff3f0547d6ac41c72c338
|
|
| MD5 |
ae091ff785be2bfded3a8ebca7aaec59
|
|
| BLAKE2b-256 |
ebc2f626fd3df4d41e435ad5f09a0487ba089d74f829358633af41758252b42e
|