diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml new file mode 100644 index 0000000..18dbc48 --- /dev/null +++ b/.github/workflows/black.yml @@ -0,0 +1,14 @@ +name: Lint + +on: [ push, pull_request ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: psf/black@stable + with: + options: "--check --verbose" + src: "pesuacademy" + diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build-docs.yml similarity index 100% rename from .github/workflows/build_docs.yml rename to .github/workflows/build-docs.yml diff --git a/.github/workflows/python-package-pip.yml b/.github/workflows/python-package-pip.yml index a129dcb..2694b13 100644 --- a/.github/workflows/python-package-pip.yml +++ b/.github/workflows/python-package-pip.yml @@ -1,6 +1,6 @@ -name: Python Package using Pip +name: Python Package using Pip -on: [push, pull_request] +on: [ push, pull_request ] jobs: build-linux: @@ -8,21 +8,21 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: [ "3.9", "3.10", "3.11" ] steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install -r requirements.txt - - name: Lint with flake8 - run: | - pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics \ No newline at end of file + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install -r requirements.txt + - name: Lint with flake8 + run: | + pip install flake8 + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics \ No newline at end of file diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index bdaab28..4ba9ac4 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -10,7 +10,7 @@ name: Upload Python Package on: release: - types: [published] + types: [ published ] permissions: contents: read @@ -21,19 +21,19 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - name: Build package - run: python -m build - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/README.md b/README.md index 146822a..5ae5d6f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,24 @@ -# PESU Academy API +# pesuacademy-py + +![PyPI](https://img.shields.io/pypi/v/pesuacademy?label=pypi%20package) +![GitHub Release](https://img.shields.io/github/v/release/HackerSpace-PESU/pesuacademy-py) +![GitHub Tag](https://img.shields.io/github/v/tag/HackerSpace-PESU/pesuacademy-py) +![PyPI - Status](https://img.shields.io/pypi/status/pesuacademy) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pesuacademy) + +![GitHub commit activity](https://img.shields.io/github/commit-activity/w/HackerSpace-PESU/pesuacademy-py) +![GitHub last commit](https://img.shields.io/github/last-commit/HackerSpace-PESU/pesuacademy-py) +![GitHub commits since latest release](https://img.shields.io/github/commits-since/HackerSpace-PESU/pesuacademy-py/latest) + +![black.yml](https://github.com/HackerSpace-PESU/pesuacademy-py/actions/workflows/black.yml/badge.svg) +![python-package-pip.yml](https://github.com/HackerSpace-PESU/pesuacademy-py/actions/workflows/python-package-pip.yml/badge.svg) +![python-publish.yml](https://github.com/HackerSpace-PESU/pesuacademy-py/actions/workflows/python-publish.yml/badge.svg) +![build-docs.yml](https://github.com/HackerSpace-PESU/pesuacademy-py/actions/workflows/build-docs.yml/badge.svg) Python wrapper and APIs for the PESU Academy website. -The wrapper requires the user's credentials to authenticate and provide **read-only** access to all the pages and -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. +The wrapper provides **read-only** access to all the pages and information accessible on the PESU Academy website. +Without authentication, 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 PES University. Use at your own risk. @@ -30,6 +44,7 @@ python setup.py install from pesuacademy import PESUAcademy p = PESUAcademy("PRN_or_SRN", "password") +# p = PESUAcademy() # Without authentication: can only fetch details from the `Know Your Class and Section` page profile = p.profile() courses = p.courses(semester=2) attendance = p.attendance() diff --git a/pesuacademy/__init__.py b/pesuacademy/__init__.py index 6f7238c..c01db1f 100644 --- a/pesuacademy/__init__.py +++ b/pesuacademy/__init__.py @@ -1,3 +1,3 @@ -__version__ = "0.0.1" +__version__ = "0.0.2" from .pesuacademy import PESUAcademy diff --git a/pesuacademy/models/__init__.py b/pesuacademy/models/__init__.py index f8989c6..aff3c33 100644 --- a/pesuacademy/models/__init__.py +++ b/pesuacademy/models/__init__.py @@ -7,5 +7,5 @@ ParentDetails, ParentInformation, AddressDetails, - QualifyingExamination + QualifyingExamination, ) diff --git a/pesuacademy/models/course.py b/pesuacademy/models/course.py index 0b7d657..0c9ac21 100644 --- a/pesuacademy/models/course.py +++ b/pesuacademy/models/course.py @@ -3,10 +3,10 @@ class Attendance: def __init__( - self, - attended_classes: Optional[int] = None, - total_classes: Optional[int] = None, - percentage: Optional[float] = None + self, + attended_classes: Optional[int] = None, + total_classes: Optional[int] = None, + percentage: Optional[float] = None, ): self.attended_classes = attended_classes self.total_classes = total_classes @@ -17,8 +17,14 @@ def __str__(self): class Course: - def __init__(self, code: str, title: str, _type: Optional[str] = None, status: Optional[str] = None, - attendance: Optional[Attendance] = None): + def __init__( + self, + code: str, + title: str, + _type: Optional[str] = None, + status: Optional[str] = None, + attendance: Optional[Attendance] = None, + ): self.code = code self.title = title self.type = _type diff --git a/pesuacademy/models/profile.py b/pesuacademy/models/profile.py index c95aaea..c1d34d5 100644 --- a/pesuacademy/models/profile.py +++ b/pesuacademy/models/profile.py @@ -4,16 +4,16 @@ 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: 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 @@ -31,18 +31,18 @@ def __str__(self): 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: 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 @@ -83,14 +83,14 @@ def __str__(self): class ParentInformation: def __init__( - self, - name: str, - mobile: str, - email: str, - occupation: str, - qualification: str, - designation: str, - employer: str + self, + name: str, + mobile: str, + email: str, + occupation: str, + qualification: str, + designation: str, + employer: str, ): self.name = name self.mobile = mobile @@ -102,11 +102,7 @@ def __init__( class ParentDetails: - def __init__( - self, - mother: ParentInformation, - father: ParentInformation - ): + def __init__(self, mother: ParentInformation, father: ParentInformation): self.mother = mother self.father = father @@ -129,12 +125,12 @@ def __str__(self): class Profile: def __init__( - self, - personal_details: PersonalDetails, - other_information: OtherInformation, - qualifying_examination: QualifyingExamination, - parent_details: ParentDetails, - address_details: AddressDetails + 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 diff --git a/pesuacademy/pages/attendance.py b/pesuacademy/pages/attendance.py index c25fbc9..924f389 100644 --- a/pesuacademy/pages/attendance.py +++ b/pesuacademy/pages/attendance.py @@ -9,7 +9,9 @@ class AttendancePageHandler: @staticmethod - def get_attendance_in_semester(session: requests_html.HTMLSession, semester_value: Optional[int] = None): + def get_attendance_in_semester( + session: requests_html.HTMLSession, semester_value: Optional[int] = None + ): try: url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin" query = { @@ -31,26 +33,33 @@ def get_attendance_in_semester(session: requests_html.HTMLSession, semester_valu 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': + 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('/'))) + 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)) + course = Course( + course_code, + course_title, + attendance=Attendance(attended_classes, total_classes, percentage), + ) attendance.append(course) return attendance @staticmethod def get_page( - session: requests_html.HTMLSession, - semester_ids: dict + session: requests_html.HTMLSession, semester_ids: dict ) -> dict[int, list[Course]]: attendance = dict() for semester_number in semester_ids: diff --git a/pesuacademy/pages/courses.py b/pesuacademy/pages/courses.py index 0036846..2a70427 100644 --- a/pesuacademy/pages/courses.py +++ b/pesuacademy/pages/courses.py @@ -9,7 +9,9 @@ class CoursesPageHandler: @staticmethod - def get_courses_in_semester(session: requests_html.HTMLSession, semester_id: Optional[int] = None): + def get_courses_in_semester( + session: requests_html.HTMLSession, semester_id: Optional[int] = None + ): try: url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin" query = { @@ -31,7 +33,10 @@ def get_courses_in_semester(session: requests_html.HTMLSession, semester_id: Opt 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': + 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() @@ -43,12 +48,13 @@ def get_courses_in_semester(session: requests_html.HTMLSession, semester_id: Opt @staticmethod def get_page( - session: requests_html.HTMLSession, - semester_ids: dict + 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_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 8e67877..86d14ca 100644 --- a/pesuacademy/pages/profile.py +++ b/pesuacademy/pages/profile.py @@ -11,7 +11,9 @@ class ProfilePageHandler: @staticmethod def get_page(session: requests_html.HTMLSession) -> Profile: try: - profile_url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin" + profile_url = ( + "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin" + ) query = { "menuId": "670", "url": "studentProfilePESUAdmin", diff --git a/pesuacademy/pesuacademy.py b/pesuacademy/pesuacademy.py index 47b17b1..9032018 100644 --- a/pesuacademy/pesuacademy.py +++ b/pesuacademy/pesuacademy.py @@ -4,7 +4,7 @@ from bs4 import BeautifulSoup from pesuacademy import util -from pesuacademy.handler import PageHandler +from pesuacademy.util.page import PageHandler from .exceptions import CSRFTokenError, AuthenticationError from .models import Profile, ClassAndSectionInfo, Course @@ -31,10 +31,12 @@ def __init__(self, username: Optional[str] = None, password: Optional[str] = Non def authenticated(self): return self._authenticated - def generate_csrf_token(self, username: Optional[str] = None, password: Optional[str] = None) -> str: + 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. @@ -47,7 +49,9 @@ def generate_csrf_token(self, username: Optional[str] = None, password: Optional csrf_token = soup.find("meta", attrs={"name": "csrf-token"})["content"] except Exception: self.__session.close() - raise CSRFTokenError("Unable to fetch default csrf token. Please try again later.") + raise CSRFTokenError( + "Unable to fetch default csrf token. Please try again later." + ) if username and password: # Prepare the login data for auth call @@ -62,12 +66,16 @@ def generate_csrf_token(self, username: Optional[str] = None, password: Optional soup = BeautifulSoup(response.text, "lxml") except Exception as e: self.__session.close() - raise AuthenticationError("Unable to authenticate. Please check your credentials.") + raise AuthenticationError( + "Unable to authenticate. Please check your credentials." + ) # if class login-form is present, login failed if soup.find("div", attrs={"class": "login-form"}): self.__session.close() - raise AuthenticationError("Invalid username or password, or the user does not exist.") + raise AuthenticationError( + "Invalid username or password, or the user does not exist." + ) # if login is successful, update the CSRF token csrf_token = soup.find("meta", attrs={"name": "csrf-token"})["content"] @@ -100,17 +108,17 @@ def know_your_class_and_section(self, username: str) -> ClassAndSectionInfo: "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "x-csrf-token": self._csrf_token, - "x-requested-with": "XMLHttpRequest" + "x-requested-with": "XMLHttpRequest", }, - data={ - "loginId": username - } + data={"loginId": username}, ) except Exception: raise ValueError("Unable to get profile from Know Your Class and Section.") soup = BeautifulSoup(response.text, "html.parser") - profile = util.profile.create_class_and_section_object_from_know_your_class_and_section(soup) + profile = util.profile.create_class_and_section_object_from_know_your_class_and_section( + soup + ) return profile def profile(self) -> Profile: @@ -139,7 +147,7 @@ def courses(self, semester: Optional[int] = None) -> dict[int, list[Course]]: def attendance(self, semester: Optional[int] = None) -> dict[int, list[Course]]: """ Get the attendance in courses of the currently authenticated user. - + :param semester: The semester number. If not provided, attendance across all semesters are returned. :return: The attendance information for the given semester. """ diff --git a/pesuacademy/handler.py b/pesuacademy/util/page.py similarity index 79% rename from pesuacademy/handler.py rename to pesuacademy/util/page.py index 3cbadd6..c46018a 100644 --- a/pesuacademy/handler.py +++ b/pesuacademy/util/page.py @@ -32,9 +32,11 @@ def set_semester_id_to_number_mapping(self, csrf_token: str): "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" + "x-requested-with": "XMLHttpRequest", } - response = self.__session.get(url, allow_redirects=False, params=query, headers=headers) + 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: @@ -45,18 +47,28 @@ def set_semester_id_to_number_mapping(self, csrf_token: str): 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)) + 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: + 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]} + 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) diff --git a/pesuacademy/util/profile.py b/pesuacademy/util/profile.py index 17ca534..df5e6f4 100644 --- a/pesuacademy/util/profile.py +++ b/pesuacademy/util/profile.py @@ -10,7 +10,7 @@ QualifyingExamination, ParentDetails, ParentInformation, - AddressDetails + AddressDetails, ) @@ -22,8 +22,12 @@ def get_data_from_section(soup: BeautifulSoup) -> dict: """ 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"}) + 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: @@ -37,7 +41,9 @@ def get_data_from_section(soup: BeautifulSoup) -> dict: return data -def create_class_and_section_object_from_know_your_class_and_section(soup: BeautifulSoup) -> ClassAndSectionInfo: +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. @@ -56,30 +62,51 @@ def create_class_and_section_object_from_know_your_class_and_section(soup: Beaut return ClassAndSectionInfo(**profile_data) -def create_personal_details_object_from_profile_page(soup: BeautifulSoup) -> PersonalDetails: +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"}) + 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"]: + 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() + 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 = element.find( + value_tag, attrs={"class": value_class_name} + ).text.strip() value = None if value == "NA" else value personal_details[key] = value @@ -95,50 +122,70 @@ def create_personal_details_object_from_profile_page(soup: BeautifulSoup) -> Per 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"] + name_as_in_aadhar=personal_details["name_as_in_aadhar"], ) -def create_other_information_object_from_profile_page(soup: BeautifulSoup) -> OtherInformation: +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_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"] + 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: +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_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 + 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: +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_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( @@ -148,23 +195,29 @@ def create_parent_details_object_from_profile_page(soup: BeautifulSoup) -> Paren occupation=parent_data["occupation"], qualification=parent_data["qualification"], designation=parent_data["designation"], - employer=parent_data["employer"] + employer=parent_data["employer"], ) return ParentDetails( - mother=parent_details["mother"], - father=parent_details["father"] + mother=parent_details["mother"], father=parent_details["father"] ) -def create_address_details_object_from_profile_page(soup: BeautifulSoup) -> AddressDetails: +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_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"]) + return AddressDetails( + present=address_details["present_address"], + permanent=address_details["permanent_address"], + ) def create_profile_object_from_profile_page(soup: BeautifulSoup) -> Profile: @@ -175,7 +228,9 @@ def create_profile_object_from_profile_page(soup: BeautifulSoup) -> Profile: """ 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) + 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( @@ -183,5 +238,5 @@ def create_profile_object_from_profile_page(soup: BeautifulSoup) -> Profile: other_information=other_information, qualifying_examination=qualifying_examination, parent_details=parent_details, - address_details=address_details + address_details=address_details, ) diff --git a/setup.py b/setup.py index d180b1a..a792535 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ import setuptools -VERSION = "0.0.1" +from pesuacademy import __version__ as VERSION DESCRIPTION = "Python wrapper and APIs for the PESU Academy website"