diff --git a/README.md b/README.md
index ab3c2c0..146822a 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/pesuacademy/constants/__init__.py b/pesuacademy/constants/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/pesuacademy/constants/fields.py b/pesuacademy/constants/fields.py
deleted file mode 100644
index 733684f..0000000
--- a/pesuacademy/constants/fields.py
+++ /dev/null
@@ -1,30 +0,0 @@
-PROFILE_FIELDS = {
- "Personal Details": [
- "Name",
- "PESU ID",
- "SRN",
- "Program",
- "Branch",
- "Semester",
- "Section",
- "Email ID",
- "Contact No",
- "Aadhar No",
- "Name as in aadhar"
- ],
- "Other Information": [
- "SSLC Marks",
- "PUC Marks",
- "Date Of Birth",
- "Blood Group"
- ],
- "Qualifying examination": [
- "Exam",
- "Rank",
- "Score"
- ],
- "Parent Details": [
- "Father Name",
-
- ]
-}
diff --git a/pesuacademy/handler.py b/pesuacademy/handler.py
new file mode 100644
index 0000000..3cbadd6
--- /dev/null
+++ b/pesuacademy/handler.py
@@ -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
+ 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)
diff --git a/pesuacademy/models/__init__.py b/pesuacademy/models/__init__.py
index ff825f5..f8989c6 100644
--- a/pesuacademy/models/__init__.py
+++ b/pesuacademy/models/__init__.py
@@ -1,2 +1,11 @@
from .course import Course, Attendance
-from .profile import Profile
+from .profile import (
+ Profile,
+ ClassAndSectionInfo,
+ PersonalDetails,
+ OtherInformation,
+ ParentDetails,
+ ParentInformation,
+ AddressDetails,
+ QualifyingExamination
+)
diff --git a/pesuacademy/models/profile.py b/pesuacademy/models/profile.py
index 9b690fa..c95aaea 100644
--- a/pesuacademy/models/profile.py
+++ b/pesuacademy/models/profile.py
@@ -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__}"
diff --git a/pesuacademy/pages/__init__.py b/pesuacademy/pages/__init__.py
index dec3efb..c667d39 100644
--- a/pesuacademy/pages/__init__.py
+++ b/pesuacademy/pages/__init__.py
@@ -1 +1,3 @@
-from .profile import get_profile_page
+from .attendance import AttendancePageHandler
+from .courses import CoursesPageHandler
+from .profile import ProfilePageHandler
diff --git a/pesuacademy/pages/attendance.py b/pesuacademy/pages/attendance.py
index 1a34836..c25fbc9 100644
--- a/pesuacademy/pages/attendance.py
+++ b/pesuacademy/pages/attendance.py
@@ -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
diff --git a/pesuacademy/pages/courses.py b/pesuacademy/pages/courses.py
index df85369..0036846 100644
--- a/pesuacademy/pages/courses.py
+++ b/pesuacademy/pages/courses.py
@@ -7,46 +7,48 @@
from pesuacademy.models import Course
-def get_courses_in_semester(session: requests_html.HTMLSession, semester_id: Optional[int] = None):
- try:
- url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin"
- query = {
- "menuId": "653",
- "controllerMode": "6403",
- "actionType": "38",
- "id": f"{semester_id}",
- "_": 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 profile data.")
- soup = BeautifulSoup(response.text, "lxml")
- except Exception:
- raise ConnectionError("Unable to fetch courses data.")
+class CoursesPageHandler:
+ @staticmethod
+ def get_courses_in_semester(session: requests_html.HTMLSession, semester_id: Optional[int] = None):
+ try:
+ url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin"
+ query = {
+ "menuId": "653",
+ "controllerMode": "6403",
+ "actionType": "38",
+ "id": f"{semester_id}",
+ "_": 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 profile data.")
+ soup = BeautifulSoup(response.text, "lxml")
+ except Exception:
+ raise ConnectionError("Unable to fetch courses data.")
- courses = []
- table = soup.find("table", attrs={"class": "table table-hover 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() == 'No\n\t\t\t\t\t\tsubjects found':
- break
- course_code = columns[0].text.strip()
- course_title = columns[1].text.strip()
- course_type = columns[2].text.strip()
- course_status = columns[3].text.strip()
- course = Course(course_code, course_title, course_type, course_status)
- courses.append(course)
- return courses
+ courses = []
+ table = soup.find("table", attrs={"class": "table table-hover 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() == 'No\n\t\t\t\t\t\tsubjects found':
+ break
+ course_code = columns[0].text.strip()
+ course_title = columns[1].text.strip()
+ course_type = columns[2].text.strip()
+ course_status = columns[3].text.strip()
+ course = Course(course_code, course_title, course_type, course_status)
+ courses.append(course)
+ return courses
-
-def get_courses_page(
- session: requests_html.HTMLSession,
- semester_ids: dict
-) -> dict[int, list[Course]]:
- courses = dict()
- for semester_number in semester_ids:
- courses_in_semester = get_courses_in_semester(session, semester_ids[semester_number])
- courses[semester_number] = courses_in_semester
- courses = dict(sorted(courses.items()))
- return courses
+ @staticmethod
+ def get_page(
+ session: requests_html.HTMLSession,
+ semester_ids: dict
+ ) -> dict[int, list[Course]]:
+ courses = dict()
+ for semester_number in semester_ids:
+ courses_in_semester = CoursesPageHandler.get_courses_in_semester(session, semester_ids[semester_number])
+ courses[semester_number] = courses_in_semester
+ courses = dict(sorted(courses.items()))
+ return courses
diff --git a/pesuacademy/pages/profile.py b/pesuacademy/pages/profile.py
index e7da064..8e67877 100644
--- a/pesuacademy/pages/profile.py
+++ b/pesuacademy/pages/profile.py
@@ -3,45 +3,28 @@
import requests_html
from bs4 import BeautifulSoup
+from pesuacademy import util
from pesuacademy.models import Profile
-def get_profile_page(session: requests_html.HTMLSession) -> Profile:
- try:
- profile_url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin"
- query = {
- "menuId": "670",
- "url": "studentProfilePESUAdmin",
- "controllerMode": "6414",
- "actionType": "5",
- "id": "0",
- "selectedData": "0",
- "_": str(int(datetime.datetime.now().timestamp() * 1000)),
- }
- response = session.get(profile_url, allow_redirects=False, params=query)
- if response.status_code != 200:
+class ProfilePageHandler:
+ @staticmethod
+ def get_page(session: requests_html.HTMLSession) -> Profile:
+ try:
+ profile_url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin"
+ query = {
+ "menuId": "670",
+ "url": "studentProfilePESUAdmin",
+ "controllerMode": "6414",
+ "actionType": "5",
+ "id": "0",
+ "selectedData": "0",
+ "_": str(int(datetime.datetime.now().timestamp() * 1000)),
+ }
+ response = session.get(profile_url, allow_redirects=False, params=query)
+ if response.status_code != 200:
+ raise ConnectionError("Unable to fetch profile data.")
+ soup = BeautifulSoup(response.text, "lxml")
+ return util.profile.create_profile_object_from_profile_page(soup)
+ except Exception:
raise ConnectionError("Unable to fetch profile data.")
- soup = BeautifulSoup(response.text, "lxml")
- except Exception:
- raise ConnectionError("Unable to fetch profile data.")
-
- profile = Profile()
- for element in soup.find_all("div", attrs={"class": "form-group"}):
- key = element.find("label",
- attrs={"class": "col-md-12 col-xs-12 control-label lbl-title-light text-left"})
- if key is None:
- continue
- else:
- key = key.text.strip()
- if element.text.strip() in ["Email ID", "Contact No", "Aadhar No", "Name as in aadhar"]:
- value_tag = "input"
- value_class_name = "form-control"
- value = element.find(value_tag, attrs={"class": value_class_name}).attrs["value"].strip()
- else:
- value_tag = "label"
- value_class_name = "col-md-12 col-xs-12 control-label text-left"
- value = element.find(value_tag, attrs={"class": value_class_name}).text.strip()
- # TODO: Convert DOB to datetime
- # TODO: Convert numbers to floats/integers
- setattr(profile, key, value)
- return profile
diff --git a/pesuacademy/pages/utils.py b/pesuacademy/pages/utils.py
deleted file mode 100644
index f819b47..0000000
--- a/pesuacademy/pages/utils.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import datetime
-from typing import Optional
-
-import requests_html
-from bs4 import BeautifulSoup
-
-
-def get_semester_list(session: requests_html.HTMLSession, csrf_token: str, semester: Optional[int] = None):
- 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 = 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
- semester_numbers = list(map(lambda x: int(x.text.split("Sem-")[1]), option_tags))
- semesters = dict(zip(semester_numbers, semester_string_ids))
- return semesters
diff --git a/pesuacademy/pesuacademy.py b/pesuacademy/pesuacademy.py
index dbd18b0..bfedc9f 100644
--- a/pesuacademy/pesuacademy.py
+++ b/pesuacademy/pesuacademy.py
@@ -3,9 +3,10 @@
import requests_html
from bs4 import BeautifulSoup
+from pesuacademy import util
+from pesuacademy.handler import PageHandler
from .exceptions import CSRFTokenError, AuthenticationError
-from .models import Profile, Course
-from .pages import profile, courses, attendance, utils
+from .models import Profile, ClassAndSectionInfo, Course
class PESUAcademy:
@@ -21,25 +22,18 @@ def __init__(self, username: Optional[str] = None, password: Optional[str] = Non
"""
self.__session = requests_html.HTMLSession()
self._authenticated: bool = False
- self._semester_ids = dict()
+ self.page_handler = PageHandler(self.__session)
self._csrf_token: str = self.generate_csrf_token(username, password)
@property
def authenticated(self):
return self._authenticated
- 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:
- """
- 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 generate_csrf_token(self, username: Optional[str] = None, password: Optional[str] = None) -> str:
"""
Generate a CSRF token. If username and password are provided, authenticate and get the CSRF token.
+ :param username: Your SRN, PRN or email address.
+ :param password: Your password.
:return: The CSRF token.
"""
try:
@@ -75,11 +69,11 @@ def generate_csrf_token(self, username: Optional[str] = None, password: Optional
# if login is successful, update the CSRF token
csrf_token = soup.find("meta", attrs={"name": "csrf-token"})["content"]
self._authenticated = True
- self._semester_ids = utils.get_semester_list(self.__session, csrf_token)
+ self.page_handler.set_semester_id_to_number_mapping(csrf_token)
return csrf_token
- def know_your_class_and_section(self, username: str) -> Profile:
+ def know_your_class_and_section(self, username: str) -> ClassAndSectionInfo:
"""
Get the publicly visible class and section information of a student from the Know Your Class and Section page.
:param username: The SRN, PRN or email address of the student.
@@ -112,12 +106,7 @@ def know_your_class_and_section(self, username: str) -> Profile:
raise ValueError("Unable to get profile from Know Your Class and Section.")
soup = BeautifulSoup(response.text, "html.parser")
- profile = Profile()
- for th, td in zip(soup.find_all("th"), soup.find_all("td")):
- key = th.text.strip()
- value = td.text.strip()
- setattr(profile, key, value)
-
+ profile = util.profile.create_class_and_section_object_from_know_your_class_and_section(soup)
return profile
def profile(self) -> Profile:
@@ -127,7 +116,7 @@ def profile(self) -> Profile:
"""
if not self._authenticated:
raise AuthenticationError("You need to authenticate first.")
- profile_info = profile.get_profile_page(self.__session)
+ profile_info = self.page_handler.get_profile()
return profile_info
def courses(self, semester: Optional[int] = None) -> dict[int, list[Course]]:
@@ -138,8 +127,7 @@ def courses(self, semester: Optional[int] = None) -> dict[int, list[Course]]:
"""
if not self._authenticated:
raise AuthenticationError("You need to authenticate first.")
- semester_ids = self.get_semester_ids_from_semester_number(semester)
- courses_info = courses.get_courses_page(self.__session, semester_ids)
+ courses_info = self.page_handler.get_courses(semester)
return courses_info
def attendance(self, semester: Optional[int] = None) -> dict[int, list[Course]]:
@@ -150,6 +138,5 @@ def attendance(self, semester: Optional[int] = None) -> dict[int, list[Course]]:
"""
if not self._authenticated:
raise AuthenticationError("You need to authenticate first.")
- semester_ids = self.get_semester_ids_from_semester_number(semester)
- attendance_info = attendance.get_attendance_page(self.__session, semester_ids)
+ attendance_info = self.page_handler.get_attendance(semester)
return attendance_info
diff --git a/pesuacademy/util/__init__.py b/pesuacademy/util/__init__.py
new file mode 100644
index 0000000..44824cb
--- /dev/null
+++ b/pesuacademy/util/__init__.py
@@ -0,0 +1 @@
+from pesuacademy.util import profile
diff --git a/pesuacademy/util/profile.py b/pesuacademy/util/profile.py
new file mode 100644
index 0000000..17ca534
--- /dev/null
+++ b/pesuacademy/util/profile.py
@@ -0,0 +1,187 @@
+import datetime
+
+from bs4 import BeautifulSoup
+
+from pesuacademy.models import (
+ ClassAndSectionInfo,
+ Profile,
+ PersonalDetails,
+ OtherInformation,
+ QualifyingExamination,
+ ParentDetails,
+ ParentInformation,
+ AddressDetails
+)
+
+
+def get_data_from_section(soup: BeautifulSoup) -> dict:
+ """
+ Get the data from a row in the Profile page.
+ :param soup: The BeautifulSoup object of the row.
+ :return: The data in the row.
+ """
+ data = dict()
+ for element in soup.find_all("div", attrs={"class": "form-group"}):
+ key = element.find("label",
+ attrs={"class": "col-md-12 col-xs-12 control-label lbl-title-light text-left"})
+ if key is None:
+ continue
+ else:
+ key = key.text.strip().lower().replace(" ", "_")
+
+ value_tag = "label"
+ value_class_name = "col-md-12 col-xs-12 control-label text-left"
+ value = element.find(value_tag, attrs={"class": value_class_name}).text.strip()
+ value = None if value == "NA" else value
+ data[key] = value
+ return data
+
+
+def create_class_and_section_object_from_know_your_class_and_section(soup: BeautifulSoup) -> ClassAndSectionInfo:
+ """
+ Create a ClassAndSectionInfo object from the Know Your Class and Section page.
+ :param soup: The BeautifulSoup object of the page.
+ :return: The ClassAndSectionInfo object.
+ """
+ profile_data = dict()
+ for th, td in zip(soup.find_all("th"), soup.find_all("td")):
+ key = th.text.strip().lower()
+ value = td.text.strip()
+ value = None if value == "NA" else value
+ if key == "class":
+ key = "semester"
+ elif key == "institute name":
+ key = "institute"
+ profile_data[key] = value
+ return ClassAndSectionInfo(**profile_data)
+
+
+def create_personal_details_object_from_profile_page(soup: BeautifulSoup) -> PersonalDetails:
+ """
+ Create a PersonalDetails object from the Profile page.
+ :param soup: The BeautifulSoup object of the page.
+ :return: The PersonalDetails object.
+ """
+ personal_details = dict()
+ personal_details_section = soup.find("div", attrs={"class": "elem-info-wrapper box-shadow clearfix"})
+ for element in personal_details_section.find_all("div", attrs={"class": "form-group"}):
+ key = element.find("label",
+ attrs={"class": "col-md-12 col-xs-12 control-label lbl-title-light text-left"})
+ if key is None:
+ continue
+ else:
+ key = key.text.strip().lower().replace(" ", "_")
+
+ if element.text.strip() in ["Email ID", "Contact No", "Aadhar No", "Name as in aadhar"]:
+ value_tag = "input"
+ value_class_name = "form-control"
+ value = element.find(value_tag, attrs={"class": value_class_name}).attrs["value"].strip()
+ else:
+ value_tag = "label"
+ value_class_name = "col-md-12 col-xs-12 control-label text-left"
+ value = element.find(value_tag, attrs={"class": value_class_name}).text.strip()
+
+ value = None if value == "NA" else value
+ personal_details[key] = value
+
+ return PersonalDetails(
+ name=personal_details["name"],
+ prn=personal_details["pesu_id"],
+ srn=personal_details["srn"],
+ program=personal_details["program"],
+ branch=personal_details["branch"],
+ semester=personal_details["semester"],
+ section=personal_details["section"],
+ email=personal_details["email_id"],
+ mobile=personal_details["contact_no"],
+ aadhar=personal_details["aadhar_no"],
+ name_as_in_aadhar=personal_details["name_as_in_aadhar"]
+ )
+
+
+def create_other_information_object_from_profile_page(soup: BeautifulSoup) -> OtherInformation:
+ """
+ Create an OtherInformation object from the Profile page.
+ :param soup: The BeautifulSoup object of the page.
+ :return: The OtherInformation object.
+ """
+ other_information_section = soup.find_all("div", attrs={"class": "dashboard-info-bar box-shadow"})[0]
+ other_information = get_data_from_section(other_information_section)
+ return OtherInformation(
+ sslc=float(other_information["sslc_marks"]),
+ puc=float(other_information["puc_marks"]),
+ dob=datetime.datetime.strptime(other_information["date_of_birth"], "%d- %m- %Y").date(),
+ blood_group=other_information["blood_group"]
+ )
+
+
+def create_qualifying_examination_object_from_profile_page(soup: BeautifulSoup) -> QualifyingExamination:
+ """
+ Create a QualifyingExamination object from the Profile page.
+ :param soup: The BeautifulSoup object of the page.
+ :return: The QualifyingExamination object.
+ """
+ qualifying_examination_section = soup.find_all("div", attrs={"class": "dashboard-info-bar box-shadow"})[1]
+ qualifying_examination = get_data_from_section(qualifying_examination_section)
+ return QualifyingExamination(
+ exam=qualifying_examination["exam"],
+ rank=int(qualifying_examination["rank"]),
+ score=float(qualifying_examination["score"]) if qualifying_examination["score"] is not None else None
+ )
+
+
+def create_parent_details_object_from_profile_page(soup: BeautifulSoup) -> ParentDetails:
+ """
+ Create a ParentDetails object from the Profile page.
+ :param soup: The BeautifulSoup object of the page.
+ :return: The ParentDetails object.
+ """
+ parent_details = {"mother": None, "father": None}
+ parent_details_section = soup.find_all("div", attrs={"class": "elem-info-wrapper box-shadow clearfix"})[1]
+ for parent_section in parent_details_section.find_all("div", attrs={"class": "col-md-6"}):
+ parent_data = get_data_from_section(parent_section)
+ parent_type = "mother" if "mother_name" in parent_data else "father"
+ parent_details[parent_type] = ParentInformation(
+ name=parent_data[f"{parent_type}_name"],
+ mobile=parent_data["mobile"],
+ email=parent_data["email"],
+ occupation=parent_data["occupation"],
+ qualification=parent_data["qualification"],
+ designation=parent_data["designation"],
+ employer=parent_data["employer"]
+ )
+ return ParentDetails(
+ mother=parent_details["mother"],
+ father=parent_details["father"]
+ )
+
+
+def create_address_details_object_from_profile_page(soup: BeautifulSoup) -> AddressDetails:
+ """
+ Create an AddressDetails object from the Profile page.
+ :param soup: The BeautifulSoup object of the page.
+ :return: The AddressDetails object.
+ """
+ address_details_section = soup.find_all("div", attrs={"class": "dashboard-info-bar box-shadow"})[2]
+ address_details = get_data_from_section(address_details_section)
+ return AddressDetails(present=address_details["present_address"], permanent=address_details["permanent_address"])
+
+
+def create_profile_object_from_profile_page(soup: BeautifulSoup) -> Profile:
+ """
+ Create a Profile object from the Profile page.
+ :param soup: The BeautifulSoup object of the page.
+ :return: The Profile object.
+ """
+ personal_details = create_personal_details_object_from_profile_page(soup)
+ other_information = create_other_information_object_from_profile_page(soup)
+ qualifying_examination = create_qualifying_examination_object_from_profile_page(soup)
+ parent_details = create_parent_details_object_from_profile_page(soup)
+ address_details = create_address_details_object_from_profile_page(soup)
+ return Profile(
+ personal_details=personal_details,
+ other_information=other_information,
+ qualifying_examination=qualifying_examination,
+ parent_details=parent_details,
+ address_details=address_details
+ )