Skip to content

Commit

Permalink
Improve Page Loading & Profile Structure (#6)
Browse files Browse the repository at this point in the history
* Add modular breakdown of page loading

* Added object approach to profile info

* Remove unused variables
  • Loading branch information
aditeyabaral authored Apr 19, 2024
1 parent 54e5c66 commit 33759f6
Show file tree
Hide file tree
Showing 14 changed files with 541 additions and 227 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The wrapper requires the user's credentials to authenticate and provide **read-o
information accessible on the PESU Academy website. Without the credentials, the wrapper will only be able to fetch
details from the `Know Your Class and Section` page.

> :warning: **Warning:** This is not an official API and is not endorsed by PESU. Use at your own risk.
> :warning: **Warning:** This is not an official API and is not endorsed by PES University. Use at your own risk.
## Installation

Expand Down
Empty file removed pesuacademy/constants/__init__.py
Empty file.
30 changes: 0 additions & 30 deletions pesuacademy/constants/fields.py

This file was deleted.

70 changes: 70 additions & 0 deletions pesuacademy/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import datetime
from typing import Optional

import requests_html
from bs4 import BeautifulSoup

from pesuacademy import pages


class PageHandler:
def __init__(self, session: requests_html.HTMLSession):
self.__session = session
self._semester_ids = dict()
self.course_page_handler = pages.CoursesPageHandler()
self.attendance_page_handler = pages.AttendancePageHandler()
self.profile_page_handler = pages.ProfilePageHandler()

def set_semester_id_to_number_mapping(self, csrf_token: str):
try:
url = "https://www.pesuacademy.com/Academy/a/studentProfilePESU/getStudentSemestersPESU"
query = {"_": str(int(datetime.datetime.now().timestamp() * 1000))}
headers = {
"accept": "*/*",
"accept-language": "en-IN,en-US;q=0.9,en-GB;q=0.8,en;q=0.7",
"content-type": "application/x-www-form-urlencoded",
"referer": "https://www.pesuacademy.com/Academy/s/studentProfilePESU",
"sec-ch-ua": '"Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "Windows",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
"x-csrf-token": csrf_token,
"x-requested-with": "XMLHttpRequest"
}
response = self.__session.get(url, allow_redirects=False, params=query, headers=headers)
if response.status_code != 200:
raise ConnectionError("Unable to fetch course data.")
except Exception:
raise ConnectionError("Unable to fetch course data.")

option_tags = response.json()
option_tags = BeautifulSoup(option_tags, "lxml")
option_tags = option_tags.find_all("option")
semester_string_ids = list(map(lambda x: x.attrs["value"], option_tags))
# TODO: Handle CIE semesters (sometimes the tag is <option value="972">CIE - Level2 (Odd Sem)</option>
semester_numbers = list(map(lambda x: int(x.text.split("Sem-")[1]), option_tags))
semesters = dict(zip(semester_numbers, semester_string_ids))
self._semester_ids = semesters

def get_semester_ids_from_semester_number(self, semester: Optional[int] = None) -> dict:
"""
Get the semester ids from the semester number. If semester is not provided, all semester ids are returned.
:param semester: The semester number.
:return: The semester ids mapping.
"""
assert semester is None or 1 <= semester <= 8, "Semester number should be between 1 and 8."
return self._semester_ids if semester is None else {semester: self._semester_ids[semester]}

def get_profile(self):
return self.profile_page_handler.get_page(self.__session)

def get_courses(self, semester: Optional[int] = None):
semester_ids = self.get_semester_ids_from_semester_number(semester)
return self.course_page_handler.get_page(self.__session, semester_ids)

def get_attendance(self, semester: Optional[int] = None):
semester_ids = self.get_semester_ids_from_semester_number(semester)
return self.attendance_page_handler.get_page(self.__session, semester_ids)
11 changes: 10 additions & 1 deletion pesuacademy/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
from .course import Course, Attendance
from .profile import Profile
from .profile import (
Profile,
ClassAndSectionInfo,
PersonalDetails,
OtherInformation,
ParentDetails,
ParentInformation,
AddressDetails,
QualifyingExamination
)
146 changes: 142 additions & 4 deletions pesuacademy/models/profile.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,146 @@
# TODO: Add a list of fields and assign them only. Do not allow any other fields to be added.
# TODO: Make separate profile for KYCAS and Profile
import datetime
from typing import Optional


class ClassAndSectionInfo:
def __init__(
self,
prn: str,
srn: str,
name: str,
semester: str,
section: str,
department: str,
branch: str,
institute: str,
cycle: Optional[str] = None
):
self.prn = prn
self.srn = srn
self.name = name
self.semester = semester
self.section = section
self.cycle = cycle
self.department = department
self.branch = branch
self.institute = institute

def __str__(self):
return f"{self.__dict__}"


class PersonalDetails:
def __init__(
self,
name: str,
prn: str,
srn: str,
branch: str,
semester: str,
section: str,
program: Optional[str] = None,
email: Optional[str] = None,
mobile: Optional[str] = None,
aadhar: Optional[str] = None,
name_as_in_aadhar: Optional[str] = None
):
self.name = name
self.prn = prn
self.srn = srn
self.program = program
self.branch = branch
self.semester = semester
self.section = section
self.email = email
self.mobile = mobile
self.aadhar = aadhar
self.name_as_in_aadhar = name_as_in_aadhar

def __str__(self):
return f"{self.__dict__}"


class OtherInformation:
def __init__(self, sslc: float, puc: float, dob: datetime.date, blood_group: str):
self.sslc = sslc
self.puc = puc
self.dob = dob
self.blood_group = blood_group

def __str__(self):
return f"{self.__dict__}"


class QualifyingExamination:
def __init__(self, exam: str, rank: int, score: float):
self.exam = exam
self.rank = rank
self.score = score

def __str__(self):
return f"{self.__dict__}"


class ParentInformation:
def __init__(
self,
name: str,
mobile: str,
email: str,
occupation: str,
qualification: str,
designation: str,
employer: str
):
self.name = name
self.mobile = mobile
self.email = email
self.occupation = occupation
self.qualification = qualification
self.designation = designation
self.employer = employer


class ParentDetails:
def __init__(
self,
mother: ParentInformation,
father: ParentInformation
):
self.mother = mother
self.father = father

def __str__(self):
return f"{self.__dict__}"


def __str__(self):
return f"{self.__dict__}"


class AddressDetails:
def __init__(self, present: str, permanent: str):
self.present = present
self.permanent = permanent

def __str__(self):
return f"{self.__dict__}"


class Profile:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __init__(
self,
personal_details: PersonalDetails,
other_information: OtherInformation,
qualifying_examination: QualifyingExamination,
parent_details: ParentDetails,
address_details: AddressDetails
):
self.personal_details = personal_details
self.other_information = other_information
self.qualifying_examination = qualifying_examination
self.parent_details = parent_details
self.address_details = address_details

def __str__(self):
return f"{self.__dict__}"
4 changes: 3 additions & 1 deletion pesuacademy/pages/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .profile import get_profile_page
from .attendance import AttendancePageHandler
from .courses import CoursesPageHandler
from .profile import ProfilePageHandler
97 changes: 51 additions & 46 deletions pesuacademy/pages/attendance.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,56 @@
from pesuacademy.models import Course, Attendance


def get_attendance_in_semester(session: requests_html.HTMLSession, semester_value: Optional[int] = None):
try:
url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin"
query = {
"menuId": "660",
"controllerMode": "6407",
"actionType": "8",
"batchClassId": f"{semester_value}",
"_": str(int(datetime.datetime.now().timestamp() * 1000)),
}
response = session.get(url, allow_redirects=False, params=query)
if response.status_code != 200:
raise ConnectionError("Unable to fetch attendance data.")
soup = BeautifulSoup(response.text, "lxml")
except Exception:
raise ConnectionError("Unable to fetch profile data.")
class AttendancePageHandler:
@staticmethod
def get_attendance_in_semester(session: requests_html.HTMLSession, semester_value: Optional[int] = None):
try:
url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin"
query = {
"menuId": "660",
"controllerMode": "6407",
"actionType": "8",
"batchClassId": f"{semester_value}",
"_": str(int(datetime.datetime.now().timestamp() * 1000)),
}
response = session.get(url, allow_redirects=False, params=query)
if response.status_code != 200:
raise ConnectionError("Unable to fetch attendance data.")
soup = BeautifulSoup(response.text, "lxml")
except Exception:
raise ConnectionError("Unable to fetch profile data.")

attendance = []
table = soup.find("table", attrs={"class": "table box-shadow"})
table_body = table.find("tbody")
for row in table_body.find_all("tr"):
columns = row.find_all("td")
if len(columns) == 1 and columns[0].text.strip() == 'Data Not\n\t\t\t\t\tAvailable':
break
course_code = columns[0].text.strip()
course_title = columns[1].text.strip()
attended_and_total_classes = columns[2].text.strip()
if '/' in attended_and_total_classes:
attended_classes, total_classes = list(map(int, attended_and_total_classes.split('/')))
else:
attended_classes, total_classes = None, None
percentage = columns[3].text.strip()
percentage = float(percentage) if percentage != "NA" else None
course = Course(course_code, course_title, attendance=Attendance(attended_classes, total_classes, percentage))
attendance.append(course)
return attendance
attendance = []
table = soup.find("table", attrs={"class": "table box-shadow"})
table_body = table.find("tbody")
for row in table_body.find_all("tr"):
columns = row.find_all("td")
if len(columns) == 1 and columns[0].text.strip() == 'Data Not\n\t\t\t\t\tAvailable':
break
course_code = columns[0].text.strip()
course_title = columns[1].text.strip()
attended_and_total_classes = columns[2].text.strip()
if '/' in attended_and_total_classes:
attended_classes, total_classes = list(map(int, attended_and_total_classes.split('/')))
else:
attended_classes, total_classes = None, None
percentage = columns[3].text.strip()
percentage = float(percentage) if percentage != "NA" else None
course = Course(course_code, course_title,
attendance=Attendance(attended_classes, total_classes, percentage))
attendance.append(course)
return attendance


def get_attendance_page(
session: requests_html.HTMLSession,
semester_ids: dict
) -> dict[int, list[Course]]:
attendance = dict()
for semester_number in semester_ids:
attendance_in_semester = get_attendance_in_semester(session, semester_ids[semester_number])
attendance[semester_number] = attendance_in_semester
attendance = dict(sorted(attendance.items()))
return attendance
@staticmethod
def get_page(
session: requests_html.HTMLSession,
semester_ids: dict
) -> dict[int, list[Course]]:
attendance = dict()
for semester_number in semester_ids:
attendance_in_semester = AttendancePageHandler.get_attendance_in_semester(
session, semester_ids[semester_number]
)
attendance[semester_number] = attendance_in_semester
attendance = dict(sorted(attendance.items()))
return attendance
Loading

0 comments on commit 33759f6

Please sign in to comment.