Skip to content
Β 
Β 

Latest commit

Β 

History

228 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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.

PyPI version Python 3.10+

✨ 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 -r requirements.txt

Testing

# Run all tests (187 tests)
pytest tests/ -v

# Run without coverage (faster)
pytest tests/ --no-cov -v

# Run specific test categories
pytest tests/core/ -v                    # Core models & exceptions (95 tests)
pytest tests/services/ -v                # Business logic services (40 tests)
pytest tests/server/ -v                  # Helper functions (26 tests)
pytest tests/utils/ -v                   # Configuration (26 tests)

# Run with coverage report
pytest tests/ --cov=src/uoapi --cov-report=html --cov-report=term-missing

# Type checking
mypy src/

# Linting
flake8 src/ tests/

# Code formatting
black src/ tests/

# Security scan
bandit -r src/

# All checks at once
make check-all

Comprehensive Test Suite

  • 187 passing tests with zero warnings
  • 95 core tests: Pydantic models (41) and exception hierarchy (54)
  • 40 service tests: DefaultCourseService (30) and DefaultTimetableService (10)
  • 26 helper tests: Helper function validation
  • 26 config tests: Configuration management
  • 100% mocked dependencies: No network calls, pure unit tests
  • 70% minimum coverage enforced

Code Quality

The refactored codebase maintains high code quality with:

  • 100% type coverage with mypy
  • 187 passing unit tests with comprehensive coverage
  • Consistent formatting with black
  • Clean imports and modular design
  • Zero warnings in test output
  • Pydantic V2 type-safe models with field validators

πŸ”„ 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

  1. Import errors: Ensure Python 3.10+ and proper installation
  2. University not supported: Check service.get_all_universities()
  3. Term validation: Use timetable_service.get_available_terms() first
  4. Rate limiting: Reduce concurrent workers if getting blocked
  5. 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-server to 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:

  1. Add Universities: Implement UniversityProvider interface
  2. Add Features: Extend service classes with new functionality
  3. Add API Endpoints: Create new REST API endpoints
  4. 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

About

A Python CLI tool for retrieving public data from Canadian universities, including the University of Ottawa and Carleton University

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages