Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.6.0] - 2026-09-17¶
A pass over how the client talks to VTOP, driven by a read-only exploration of the live portal rather than by reading our own code. Several things we believed turned out not to be true.
Added¶
VtopClient.restore(registration_number, cookie, csrf_token, user_agent=None)and asessionproperty returning theRestorableSessionit takes. VTOP keeps the session server side againstJSESSIONID, so a client holding that cookie and the post-login CSRF token can make data requests without ever authenticating — verified from a process that had never logged in. This is what lets a caller stay stateless instead of holding a live client in memory, which matters most for the OTP gate: the challenge is raised in one request and answered in another.get_profile(include_grade_history=False, include_mentor=False). Both default toTrue, so nothing changes for existing callers. Grade history is the largest response the client fetches anywhere, around 137KB, and callers who only wanted a name were paying for it every time.VtopMenuUnavailableError, raised when VTOP refuses a request.Shape validation on every semester scoped method.
A documentation site at udhay-adithya.github.io/vitap-vtop-client, built with Sphinx and Furo and published from CI. The API reference is generated from the docstrings, so it cannot drift from the code.
Fixed¶
VTOP answers a rejected request with a ~1KB “This menu is not available at present!!!” fragment and an HTTP 200, so
raise_for_status()passed it and a parser then failed on markup it could not recognise. It is now detected in a response hook, which covers every fetch function including ones added later. The same body comes back whether the request shape was wrong or the menu is genuinely switched off, and the error says only that, because the response carries nothing to separate them.An expired CSRF token returns a stock Tomcat 404, not the login page. Spring refuses the request before it is routed. Session-death detection only looked for the login page, so a dead token surfaced as a bare
HTTPStatusError. It now raisesVtopSessionErrorwith a 401.httpx raises its transport errors with an empty message, so every message we built with
f"...: {e}"stopped at the colon.VtopConnectionErrornow names the underlying class, which is the only thing separating a read timeout from a refused connection.fetch_profilecaught its three requests in one block, so whichever failed was reported as the next call in the sequence: a timeout on the profile page came back blaming grade history. Each leg is now named.NCGPA_RANK_URLpointed at hostel counselling slot booking, not NCGPA rank. RenamedHOSTEL_COUNSELLING_URL.VIRTUAL_ACCOUNT_URLhad a trailing space inside the string literal.An internal flag leaked into
VtopClient’s public signature, and forcedpasswordto render as optional in the generated reference.
Changed¶
Breaking:
passwordis a required argument again. Omitting it raisesTypeError; an empty string still raisesVtopLoginError.Breaking: a malformed
sem_sub_idnow raisesVtopSessionErrorbefore any request is sent. VTOP does not reject an unknown semester id: it answers with a normal, empty result, so a wrong semester is indistinguishable from a semester with no data. The check is deliberately shallow: it catches a typo, not an id that is well formed but stale. Choosing a real one is the caller’s job, andget_semesters()is what makes that possible.Breaking:
NCGPA_RANK_URLis renamed. Nothing used it.Requests no longer send
Connection: close. It closed the socket after every response, so each request paid a fresh TCP and TLS handshake, and four calls underasyncio.gathertook as long as four serial ones — which read as VTOP serialising the session. It does not; the handshakes did. Concurrent fan-out is roughly 2.6x faster without it.Attendance, marks, exam schedule, timetable and grade view no longer post to their page shell first. On a cold session with nothing primed, every one of those data endpoints answers directly, so the extra request bought nothing. Each is now a single request. The genuine primes —
init_course_pageandprocessDigitalAssignmentbefore an upload — are untouched.get_profilefetches its nested grade history and mentor concurrently.We documented that VTOP binds a session to the User-Agent that created it. It does not: an exported session was reused successfully from a completely different agent. The agent is still pinned per client, but that is our choice for a consistent identity rather than a VTOP requirement.
Removed¶
DOCS.md. It documented 21 of 50 public methods and had drifted every time the API changed. The site replaces it.
[0.5.1] - 2026-09-17¶
Fixed¶
VtopExamScheduleError,VtopMarksError,VtopGeneralOutingErrorandVtopWeekendOutingErrorare now exported fromvitap_vtop_client.exceptions. They were defined and raised but never re-exported, so callers could not name them and had to fall back to theVitapVtopClientErrorbase.The marks module wrapped unexpected failures in
VtopAttendanceError, so a marks failure reached callers as an attendance one andVtopMarksErrorwas never raised despite being defined for it. CatchingVtopAttendanceErroraroundget_marksno longer works; catchVtopMarksErrorinstead.
[0.5.0] - 2026-08-31¶
Added¶
get_capstone_attendance(sem_sub_id)for the capstone/SDP attendance VTOP added to the attendance page. Kept separate fromget_attendancebecause it is per semester rather than per course and counts present / on duty / absent; returnsNonefor students without a capstone. Verified against a live response.The academic calendar:
get_calendar_class_groups,get_calendar_months,get_calendar_month, andget_academic_calendar, which walks all of a semester’s months and returns one flat, date-ordered list of days with their events (including named holidays).VtopCalendarError.
[0.4.0] - 2026-08-29¶
Parity pass with the lib_vtop rust crate: features that existed there but not
here, plus the fixes that running against live VTOP surfaced.
Added¶
Faculty:
search_faculty,get_all_faculty(VTOP serves the whole directory from the search endpoint whenempIdis empty) andget_faculty_details, which returns the profile and weekly office hours.Digital assignments:
get_digital_assignmentsfans out one request per course because VTOP serves the course list and the per course assignments from separate endpoints;get_course_assignmentsfetches a single course;download_assignment_fileretrieves question papers and submitted files;upload_assignmentsubmits a file andverify_assignment_upload_otpconfirms it when VTOP holds the upload for an emailed OTP. Uploads are validated before hitting the network: non empty, one of pdf, doc, docx, xls or xlsx, and at most 4MB.The course page:
init_course_page(VTOP requires it before it will answer the lookups),get_course_page_courses,get_course_page_slots,get_course_detail,download_course_materialanddownload_course_plan.get_grade_view(sem_sub_id)andget_grade_view_detail(sem_sub_id, course_id)for the StudentGradeView page, which neither this client nor the rust crate covered. The detail carries per-component marks, the total, and class statistics including the grade cutoff ranges.get_attendance_detail, the per-class register behind each attendance row.Outing writes:
submit_general_outingandsubmit_weekend_outingare now reachable on the client (the functions existed but were never exposed), alongsidedelete_general_outing,delete_weekend_outing,download_general_outing_passanddownload_weekend_outing_form.download_payment_receipt, keeping VTOP’s misspelledreceitNoquery parameter as the server expects it.GradeCourseHistoryModelandGradeHistoryModel.courses. Only the CGPA summary was parsed before, so the entire per course grade table was lost.BiometricModel.serialand.date, which the parser was discarding, plus theday,out_timeanddurationplaceholders the rust crate carries.AttendanceModel.faculty,.course_type_codeand a real.course_id, all read from thecallStudentAttendanceDetailDisplayonclick on each row.serialandcan_downloadon both outing records.can_downloadreflects whether VTOP actually offers the file: a download link for general outings, an accepted status for weekend outings, which render the link regardless.VtopClient(user_agent=...)and auser_agentproperty. VTOP binds a session to the User-Agent that created it, so it is now fixed for the life of a client and pinned onto every request, even when a fetch function passes its own headers.DEFAULT_USER_AGENTis the fallback.VtopClient.get_cookie(), returning the session’s cookies as aCookieheader value so something outside the client (an in-app VTOP WebView) can reuse the session. RaisesVtopSessionErrorwhen not logged in.VtopClient.is_authenticated.StudentProfileModel.registration_number, filled from the value captured at login — VTOP’s profile page never renders it.VtopDigitalAssignmentErrorand its file, size, type and upload OTP subclasses.A three layer test suite: parser logic over hand built markup, parser contracts over recorded fixtures, and a live layer that is deselected by default because VTOP’s login OTP means it cannot run unattended.
scripts/record_fixtures.pycaptures fixtures and scrubs registration and application numbers, names, contact details, photographs and session tokens before writing, since fixtures are committed.
Fixed¶
VTOP omits the Sectigo intermediate CA from its TLS chain. Browsers and curl fetch it automatically over AIA; Python’s
ssldoes not, so every request failed withCERTIFICATE_VERIFY_FAILEDunless the caller setSSL_CERT_FILEby hand. The intermediate now ships as package data and the client verifies against certifi’s roots plus that certificate. Verification stays fully enabled; nothing is bypassed.get_semesters()no longer returns an empty list for students without a timetable (freshers especially), which surfaced as “No semesters available” right after login. It now falls back from the timetable page to the marks and exam schedule pages, which render the full institutional semester list.Outing submit and delete no longer report success as an error. VTOP styles the pending “Waiting for Mentor’s Approval” status in red inside the requests table, which the parser read as a failure.
The general outing submit crashed before it even sent, because
parse_outing_formsubscripted aparentContactNumberinput the general form does not render. Every field except the registration number is now optional; a missing registration number means the form did not render at all, which raises a clear error naming the weekend eligibility window.The weekend outing parser assumed 13 columns and crashed on the live 11 column table, which carries the booking id only inside the download link. It now handles both the 11 and 14 column forms.
The payment receipts parser raised after VTOP inserted invoice and fee columns, shifting the amount and the view button. Columns are now located by header label and the button by its handler, skipping rows without one.
The attendance detail parser read VTOP’s header row as data, because it is rendered with
tdcells inside the same table. Rows must now lead with a numeric serial.Attendance tolerates the “attendance between” column being absent, which previously shifted
debar_statusonto the wrong cell.The grade history summary table is selected by looking for the one containing CGPA rather than taking the first table matching the class, which could pick the wrong one.
Changed¶
Breaking:
AttendanceModel.course_idnow holds the course id rather than the class number, so it can be used to request detail. The class number moved toclass_number, andwithin_attendance_percentageis nowattendance_between_percentage.Breaking:
ExamEntry.course_title,.type,.registration_number,.dateand.sessionare now.course_name,.course_type,.course_id,.exam_dateand.exam_session. The fifth column was labelledregistration_numberbut has always held the course id.Breaking:
BiometricModel.timeis nowin_time.Breaking:
GeneralOutingRequest.leave_idis""instead of the"N/A"sentinel when no leave pass is available.Breaking:
parse_outing_response(html)is nowparse_outing_response(html, page_reload_message). VTOP reloads the outing page instead of returning a confirmation, so the caller supplies the wording for the successful outcome (“applied … waiting for approval” vs “deleted”). Both submit paths now use it in place offind_outing_response, which matched one exact inline style string.
[0.3.0] - 2026-07-19¶
Added¶
The login OTP flow. VTOP gates login behind an OTP after inactivity or when signing in from a new IP address, which the client had no support for, so logins against current VTOP could not complete.
VtopClient.login()now raisesVtopLoginOtpRequiredErrorcarrying the OTP page’s CSRF token;verify_login_otp(otp)andresend_login_otp()finish the login, andotp_pendingreports whether one is outstanding.VtopLoginOtpRequiredError,VtopLoginOtpIncorrectErrorandVtopLoginOtpExpiredError.get_semesters(), which parses thesemesterSubIdselect on the timetable page, skipping the placeholder option and stripping the “- AMR” suffix.
Fixed¶
Login error handling matches on the response URL as a substring rather than by exact equality, aligning with the rust crate.
find_registration_numberwas being handed the response object instead of its text.
Changed¶
Breaking: login is now explicit. Callers must handle
VtopLoginOtpRequiredErrorand callverify_login_otp._ensure_logged_inrefuses to silently retry while an OTP is pending, since a retry would invalidate the OTP already sent.Breaking: the
SemSubIDdictionary is removed from constants. It had to be edited by hand for every new semester and was already stale, and a stale entry silently resolves to the wrong semester rather than failing. Useget_semesters()instead.
[0.2.8] - 2025-06-19¶
Changed¶
Implement retry mechanism when failed to solve captcha.
[0.2.7] - 2025-06-11¶
Changed¶
GradeHistoryModel will default to “N/A” if no values are found.
[0.2.6] - 2025-06-11¶
Changed¶
Grade History to nullable/empty.
[0.2.5] - 2025-06-01¶
Added¶
Add Pending payments retrieval
Add Payment receipts retrieval
[0.2.4] - 2025-06-01¶
Added¶
Support for custom login id’s
Changed¶
Remove registration number validation for now
[0.2.3] - 2025-05-31¶
Added¶
Add registration number validation
Add dynamic timeslots and days for timetable
Changed¶
Mask sensitive user information in login logs
[0.2.3] - 2025-05-27¶
Changed¶
Timetable parser to not include any global variables
[0.2.0] - 2025-05-24¶
Added¶
Initial release: Attendance, Timetable, Exam Schedule, Profile, Weekend and General Outing