Skip to content

feat: add official academic calendar source adapter - #255

Open
SabrinaTso wants to merge 2 commits into
NTHU-SA:mainfrom
SabrinaTso:feature/official-calendar-adapter
Open

feat: add official academic calendar source adapter#255
SabrinaTso wants to merge 2 commits into
NTHU-SA:mainfrom
SabrinaTso:feature/official-calendar-adapter

Conversation

@SabrinaTso

Copy link
Copy Markdown

Summary

  • add a read-only adapter for NTHU's public academic-calendar Google ICS feed
  • preserve source identity, calendar timezone, raw snapshot SHA-256, all-day exclusive end semantics, and UTC timed events
  • include a minimal public 115 academic-year fixture with source metadata and deterministic parser tests
  • make source failures explicit, skip/count malformed sibling VEVENTs, disable redirects, use a 10-second timeout, and cap streamed ICS input at 5 MiB

Scope

This is the source-adapter foundation for #184. It intentionally does not add /academic-calendar, cache/storage, an MCP tool, or multi-source policy yet; those can be reviewed separately once the source contract is accepted.

Verification

  • python -m pytest tests/test_academic_calendar.py — 8 passed
  • python -m pytest -n auto tests — 687 passed
  • python -m pre_commit run --all-files
  • parsed the live public ICS successfully: 2,402 valid events, 29 malformed events counted and skipped
  • final security diff review: no reportable findings

Source provenance

The runtime feed is the stable public ICS endpoint for the same nthu.acad@gmail.com Google Calendar embedded by NTHU Academic Affairs. The fixture metadata retains the official publication page, retrieval timestamp, and source snapshot digest.

@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a read-only adapter for NTHU’s official academic-calendar ICS feed.

Changes:

  • Adds bounded fetching and source-faithful ICS parsing.
  • Preserves metadata, hashes, and event time semantics.
  • Adds fixtures, tests, package exports, and the icalendar dependency.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Summary
tests/test_academic_calendar.py Adapter and parser tests. Nit (2 votes): Correct the reported verification count.
tests/fixtures/academic_calendar/official_nthu_115_excerpt.source.json Fixture provenance metadata.
tests/fixtures/academic_calendar/official_nthu_115_excerpt.ics Deterministic ICS fixture.
src/data_api/domain/academic_calendar/services.py Fetching and parsing implementation. Moderate (2 votes): Reject mixed DATE/DATE-TIME DTSTART/DTEND pairs and count them as skipped.
src/data_api/domain/academic_calendar/__init__.py Academic-calendar domain exports.
src/data_api/domain/__init__.py Registers the academic-calendar domain.
requirements.txt Adds the icalendar dependency.
Suppressed comments (2)

src/data_api/domain/academic_calendar/services.py:93

  • The try around Calendar.from_ical makes any ValueError raised while decoding one malformed VEVENT a document-level AcademicCalendarParseError. Thus an invalid DTSTART/DTEND in one sibling can prevent later valid events from being walked, contrary to the stated skip/count behavior. Use a tolerant per-component parse boundary (or otherwise retain successfully parsed siblings) so only the offending VEVENT is counted and skipped.
    try:
        calendar = Calendar.from_ical(ics_content)
    except (TypeError, ValueError) as exc:
        raise AcademicCalendarParseError(f"Unable to parse VCALENDAR document: {exc}") from exc

src/data_api/domain/academic_calendar/services.py:63

  • The 10-second timeout is configured only when this function creates active_client; an injected AsyncClient can have a different timeout or no timeout at all, so this source request can exceed the adapter's stated bound. Pass timeout=10.0 to stream (or otherwise enforce it per request) so the timeout remains an invariant for injected clients too.
        async with active_client.stream(
            "GET", OFFICIAL_NTHU_ICS_URL, follow_redirects=False
        ) as response:

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +120 to +121
end = _optional_decoded_value(component, "DTEND")
all_day = isinstance(start, date) and not isinstance(start, datetime)
class TestOfficialNTHUCalendarParser:
"""The adapter preserves source semantics instead of guessing dates."""

def test_uses_the_stable_public_google_ics_feed(self):
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants