Skip to main content

nusmods-api

A typed Python wrapper for the NUSMods API.

The client provides access to module lists, detailed module information, venue data, and shared timetable URLs from nusmods.com. Responses are converted into immutable Python dataclasses.

Requirements

  • Python 3.15 or newer
  • Internet access when making API requests

Installation

With uv

uv add nusmods-api

For local development:

uv sync

With pip

pip install nusmods-api

Quick start

from nusmods_api import NUSModsClient

client = NUSModsClient(academic_year="2026-2027")

modules = client.get_module_list()
for module in modules[:5]:
    print(module.moduleCode, module.title)

module = client.get_module("CS2030S")
print(module.title)
print(module.description)

If no academic year is supplied, the client uses the current calendar year and the following year. For example, a client created in 2026 uses 2026-2027.

Client configuration

from nusmods_api import NUSModsClient

client = NUSModsClient(
    academic_year="2026-2027",
    timeout=20.0,
    cache_directory=".nusmods_cache",
    cache_expiration=86_400,
)
Argument Default Description
academic_year Current year and next year Academic year in YYYY-YYYY format
timeout 10.0 HTTP timeout in seconds
cache_directory .nusmods_cache Directory for cached JSON responses
cache_expiration 86400 Cache lifetime in seconds; use None for no expiration

Set cache_directory=None to disable caching:

client = NUSModsClient(cache_directory=None)

API usage

Module list

modules = client.get_module_list()

for module in modules:
    print(module.moduleCode, module.title, module.semesters)

Detailed module information

modules = client.get_module_information()
module = client.get_module(" cs2030s ")

print(module.moduleCode)  # CS2030S
print(module.acadYear)
print(module.semesterData)

Module codes are stripped and normalized to uppercase by get_module.

Venues

venues = client.get_venues(semester=1)
print(venues)

venue_information = client.get_venue_information(semester=1)
for venue, entries in venue_information.items():
    print(venue, entries)

semester must be 1 or 2.

Shared timetables

Pass a NUSMods share URL to get_shared_timetable:

share_url = (
    "https://nusmods.com/timetable/sem-1/share?"
    "CS1231S=TUT:25B,LEC:1&"
    "CS2030S=REC:04,LAB:10B,LEC:1&"
    "GES1034=LEC:1,TUT:E2&"
    "IS1108=TUT:14,LEC:2&"
    "MA1522=LEC:1"
)

timetable = client.get_shared_timetable(share_url)

for lesson in timetable.weekly:
    print(
        lesson.moduleCode,
        lesson.lessonType,
        lesson.classNo,
        lesson.day,
        lesson.startTime,
        lesson.endTime,
        lesson.venue,
    )

for exam in timetable.exams:
    print(exam.moduleCode, exam.examDate, exam.examDuration)

The client parses the module selections from the URL and retrieves the corresponding module data from the NUSMods API. Lesson type codes such as LEC, TUT, LAB, and REC are mapped to their full names.

Timetable also provides convenience methods:

monday = timetable.get_day_schedule("Monday")
exam = timetable.get_exam_for_module("CS2030S")

Data models

The package exposes frozen dataclasses for common API response types:

  • ModuleCondensed — module code, title, and offered semesters
  • Module — detailed module information
  • Lesson — class schedule information
  • LessonWithModule — a lesson including its module code
  • VenueLesson and VenueInformation — venue timetable data
  • WeekRange — date-based lesson ranges
  • Workload — workload components
  • PrerequisiteTree — nested prerequisite expressions
  • ExamTimetable — exam information
  • Timetable — selected weekly lessons and exams

Some model fields retain the NUSMods API naming convention, such as moduleCode, classNo, startTime, lessonType, and examDate.

Error handling

The client raises package-specific exceptions:

from nusmods_api import (
    NUSModsHTTPError,
    NUSModsInvalidResponseError,
    NUSModsNotFoundError,
    NUSModsRequestError,
)

try:
    module = client.get_module("CS2030S")
except NUSModsNotFoundError:
    print("The module or academic year was not found")
except NUSModsHTTPError as error:
    print(error.status_code, error.url)
except NUSModsInvalidResponseError:
    print("The API returned invalid JSON")
except NUSModsRequestError:
    print("The API could not be reached")

Invalid client arguments, malformed share URLs, invalid semesters, and unmatched lesson selections raise ValueError.

Testing

Run the default unit test suite:

uv run pytest

The live integration test makes real requests to the NUSMods API:

uv run pytest tests/test_live_client.py -m live -q

The live test requires internet access and may need its expected timetable dates updated when NUSMods publishes a new academic-year dataset.

License

No license has been specified yet.

Metadata

Release files for nusmods-api 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nusmods-api 0.1.0
File Size Uploaded Provenance Actions
nusmods_api-0.1.0.tar.gz 8.7 kB Without provenance Details for nusmods_api-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for nusmods-api 0.1.0
File Interpreter ABI Platform Actions
nusmods_api-0.1.0-py3-none-any.whl Python 3 none any Details for nusmods_api-0.1.0-py3-none-any.whl

Total release size: 18.9 kB

Release files / nusmods_api-0.1.0.tar.gz

Download URL nusmods_api-0.1.0.tar.gz
Size 8.7 kB
Tags Source
SHA-256 checksum
How to use checksums
ae5f0fd43c9eee96564b0fbb5ffecf58b9b34645e696d42c99cdb216c782c6b2
BLAKE2b-256 checksum
How to use checksums
eb6f8c2d8220f9da87a0794843bb3a1700690e60049ccad1400cd7fc5b156428
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.15.0

Release files / nusmods_api-0.1.0-py3-none-any.whl

Download URL nusmods_api-0.1.0-py3-none-any.whl
Size 10.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
40e254b9b8d92e6fbe8da2dcf5b46293189ac130dece19b0bc950f778fdeb207
BLAKE2b-256 checksum
How to use checksums
29a129a546640874ac5500fe34be0621d033a1e61bfdd92a2ccb82926ceb37e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.15.0

Security for nusmods-api 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page