diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b03ab1f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 856eb97..cbca136 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,50 +1,79 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries +name: Publish -name: Upload Python Package +on: + push: + tags: ["[0-9]+.[0-9]+.[0-9]+*"] -permissions: - contents: write - id-token: write - -on: workflow_dispatch +permissions: {} jobs: - deploy: - runs-on: ubuntu-latest + tests: + uses: ./.github/workflows/tests.yml + permissions: + contents: read + build: + needs: tests + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: actions/setup-python@v7 + with: + python-version: "3.14" + - run: python -m pip install build + - run: python -m build + - name: Check the build version matches the tag + run: test -f "dist/edupage_api-${GITHUB_REF_NAME}-py3-none-any.whl" + - name: Check the tag is on master + run: git merge-base --is-ancestor "$GITHUB_SHA" origin/master + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/edupage-api - + permissions: + id-token: write steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/download-artifact@v8 with: - python-version: "3.9" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine build pdoc3 - - - name: Regenerate documentation - run: | - pdoc3 --html edupage_api - rm -rf docs - mv html/edupage_api docs - - - name: Commit and push documentation changes - uses: EndBug/add-and-commit@v9 + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + + docs: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 with: - add: "docs" - default_author: github_actions - message: "Regenerate documentation" - - - name: Build - run: python -m build - - name: Publish to pypi - uses: pypa/gh-action-pypi-publish@release/v1 - + python-version: "3.14" + - run: python -m pip install -e . pdoc + - run: pdoc edupage_api -o site + - uses: actions/upload-pages-artifact@v5 + with: + path: site/ + + deploy-docs: + needs: [docs, publish] + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b6326cb --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,27 @@ +name: Tests + +on: + push: + branches: [master] + pull_request: + workflow_call: + +permissions: + contents: read + + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.14"] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: ${{ matrix.python-version }} + - run: python -m pip install -e . + - run: python -m unittest discover -s tests -v + diff --git a/edupage_api/__init__.py b/edupage_api/__init__.py index ba71885..f713317 100644 --- a/edupage_api/__init__.py +++ b/edupage_api/__init__.py @@ -62,7 +62,7 @@ def __exit__(self, exc_type, exc_value, traceback): def login( self, username: str, password: str, subdomain: str - ) -> Optional[TwoFactorLogin]: + ) -> TwoFactorLogin | None: """Login while specifying the subdomain to log into. Args: @@ -77,7 +77,7 @@ def login( return Login(self).login(username, password, subdomain) - def login_auto(self, username: str, password: str) -> Optional[TwoFactorLogin]: + def login_auto(self, username: str, password: str) -> TwoFactorLogin | None: """Login using https://portal.edupage.org. If this doesn't work, please use `Edupage.login`. Args: @@ -100,7 +100,7 @@ def logout(self): LoginSession(self).logout() - def get_students(self) -> Optional[list[EduStudent]]: + def get_students(self) -> list[EduStudent] | None: """Get list of all students in your class. Returns: @@ -109,7 +109,7 @@ def get_students(self) -> Optional[list[EduStudent]]: return People(self).get_students() - def get_all_students(self) -> Optional[list[EduStudentSkeleton]]: + def get_all_students(self) -> list[EduStudentSkeleton] | None: """Get list of all students in your school. Returns: @@ -118,7 +118,7 @@ def get_all_students(self) -> Optional[list[EduStudentSkeleton]]: return People(self).get_all_students() - def get_teachers(self) -> Optional[list[EduTeacher]]: + def get_teachers(self) -> list[EduTeacher] | None: """Get list of all teachers in your school. Returns: @@ -127,7 +127,7 @@ def get_teachers(self) -> Optional[list[EduTeacher]]: return People(self).get_teachers() - def get_classrooms(self) -> Optional[list[Classroom]]: + def get_classrooms(self) -> list[Classroom] | None: """Get list of all classrooms in your school. Returns: @@ -136,7 +136,7 @@ def get_classrooms(self) -> Optional[list[Classroom]]: return Classrooms(self).get_classrooms() - def get_classes(self) -> Optional[list[Class]]: + def get_classes(self) -> list[Class] | None: """Get list of all classes in your school. Returns: @@ -145,7 +145,7 @@ def get_classes(self) -> Optional[list[Class]]: return Classes(self).get_classes() - def get_subjects(self) -> Optional[list[Subject]]: + def get_subjects(self) -> list[Subject] | None: """Get list of all subjects in your school. Returns: @@ -155,7 +155,7 @@ def get_subjects(self) -> Optional[list[Subject]]: return Subjects(self).get_subjects() def send_message( - self, recipients: Union[list[EduAccount], EduAccount], body: str + self, recipients: list[EduAccount] | EduAccount, body: str ) -> int: """Send message. @@ -169,7 +169,7 @@ def send_message( return Messages(self).send_message(recipients, body) - def get_my_timetable(self, date: date) -> Optional[Timetable]: + def get_my_timetable(self, date: date) -> Timetable | None: """Get timetable for the logged-in user on a specified date. Args: @@ -181,7 +181,7 @@ def get_my_timetable(self, date: date) -> Optional[Timetable]: return Timetables(self).get_my_timetable(date) - def get_meals(self, date: date) -> Optional[Meals]: + def get_meals(self, date: date) -> Meals | None: """Get lunches. Args: @@ -322,9 +322,9 @@ def get_school_year(self) -> int: def get_timetable( self, - target: Union[EduTeacher, EduStudent, Class, Classroom], + target: EduTeacher | EduStudent | Class | Classroom, date: date, - ) -> Optional[Timetable]: + ) -> Timetable | None: """Get timetable of a teacher, student, class, or classroom for a specific date. Args: @@ -348,7 +348,7 @@ def get_next_ringing_time(self, date_time: datetime) -> RingingTime: """ return RingingTimes(self).get_next_ringing_time(date_time) - def switch_to_child(self, child: Union[EduAccount, int]): + def switch_to_child(self, child: EduAccount | int): """Switch to an account of a child - can only be used on parent accounts Args: diff --git a/edupage_api/classes.py b/edupage_api/classes.py index b4c88c6..955a5e9 100644 --- a/edupage_api/classes.py +++ b/edupage_api/classes.py @@ -12,14 +12,14 @@ class Class: class_id: int name: str short: str - homeroom_teachers: Optional[list[EduTeacher]] - homeroom: Optional[Classroom] - grade: Optional[int] + homeroom_teachers: list[EduTeacher] | None + homeroom: Classroom | None + grade: int | None class Classes(Module): @ModuleHelper.logged_in - def get_classes(self) -> Optional[list]: + def get_classes(self) -> list | None: classes_list = DbiHelper(self.edupage).fetch_class_list() if classes_list is None: @@ -56,7 +56,7 @@ def get_classes(self) -> Optional[list]: return classes - def get_class(self, class_id: Union[int, str]) -> Optional[Class]: + def get_class(self, class_id: int | str) -> Class | None: try: class_id = int(class_id) except (ValueError, TypeError): diff --git a/edupage_api/classrooms.py b/edupage_api/classrooms.py index de4947f..797ae55 100644 --- a/edupage_api/classrooms.py +++ b/edupage_api/classrooms.py @@ -14,7 +14,7 @@ class Classroom: class Classrooms(Module): @ModuleHelper.logged_in - def get_classrooms(self) -> Optional[list]: + def get_classrooms(self) -> list | None: classroom_list = DbiHelper(self.edupage).fetch_classroom_list() if classroom_list is None: @@ -36,7 +36,7 @@ def get_classrooms(self) -> Optional[list]: return classrooms - def get_classroom(self, classroom_id: Union[int, str]) -> Optional[Classroom]: + def get_classroom(self, classroom_id: int | str) -> Classroom | None: try: classroom_id = int(classroom_id) except (ValueError, TypeError): diff --git a/edupage_api/compression.py b/edupage_api/compression.py index dfdc4b2..c688b8c 100644 --- a/edupage_api/compression.py +++ b/edupage_api/compression.py @@ -168,7 +168,7 @@ def __decode_data(data: str) -> str: return RequestData.chromium_base64_decode(data) @staticmethod - def encode_request_body(request_data: Union[dict, str]) -> str: + def encode_request_body(request_data: dict | str) -> str: encoded_data = ( ModuleHelper.encode_form_data(request_data) if type(request_data) == dict diff --git a/edupage_api/dbi.py b/edupage_api/dbi.py index 655c0e3..b4ea6fb 100644 --- a/edupage_api/dbi.py +++ b/edupage_api/dbi.py @@ -7,14 +7,14 @@ class DbiHelper(Module): def __get_dbi(self) -> dict: return self.edupage.data.get("dbi") - def __get_item_group(self, item_group_name: str) -> Optional[dict]: + def __get_item_group(self, item_group_name: str) -> dict | None: dbi = self.__get_dbi() if dbi is None: return None return dbi.get(item_group_name) - def __get_item_with_id(self, item_group_name: str, item_id: str) -> Optional[dict]: + def __get_item_with_id(self, item_group_name: str, item_id: str) -> dict | None: items_in_group = self.__get_item_group(item_group_name) if items_in_group is None: return None @@ -27,53 +27,53 @@ def __get_full_name(self, person_item: dict) -> str: return f"{first_name} {last_name}" - def fetch_subject_name(self, subject_id: int) -> Optional[str]: + def fetch_subject_name(self, subject_id: int) -> str | None: subject_item = self.__get_item_with_id("subjects", subject_id) if subject_item is not None: return subject_item.get("short") - def fetch_classroom_number(self, classroom_id: str) -> Optional[str]: + def fetch_classroom_number(self, classroom_id: str) -> str | None: classroom_item = self.__get_item_with_id("classrooms", classroom_id) if classroom_item is not None: return classroom_item.get("short") - def fetch_class_name(self, class_id: int) -> Optional[str]: + def fetch_class_name(self, class_id: int) -> str | None: class_item = self.__get_item_with_id("classes", class_id) if class_item is not None: return class_item.get("short") - def fetch_teacher_name(self, teacher_id: int) -> Optional[str]: + def fetch_teacher_name(self, teacher_id: int) -> str | None: teacher_item = self.__get_item_with_id("teachers", teacher_id) if teacher_item is not None: return self.__get_full_name(teacher_item) - def fetch_student_name(self, student_id: int) -> Optional[str]: + def fetch_student_name(self, student_id: int) -> str | None: student_item = self.__get_item_with_id("students", student_id) if student_item is not None: return self.__get_full_name(student_item) - def fetch_student_list(self) -> Optional[list]: + def fetch_student_list(self) -> list | None: return self.__get_item_group("students") - def fetch_teacher_list(self) -> Optional[list]: + def fetch_teacher_list(self) -> list | None: return self.__get_item_group("teachers") - def fetch_subject_list(self) -> Optional[list]: + def fetch_subject_list(self) -> list | None: return self.__get_item_group("subjects") - def fetch_classroom_list(self) -> Optional[list]: + def fetch_classroom_list(self) -> list | None: return self.__get_item_group("classrooms") - def fetch_class_list(self) -> Optional[list]: + def fetch_class_list(self) -> list | None: return self.__get_item_group("classes") - def fetch_teacher_data(self, teacher_id: int) -> Optional[dict]: + def fetch_teacher_data(self, teacher_id: int) -> dict | None: return self.__get_item_with_id("teachers", teacher_id) - def fetch_student_data(self, student_id: int) -> Optional[dict]: + def fetch_student_data(self, student_id: int) -> dict | None: return self.__get_item_with_id("students", student_id) - def fetch_student_data_by_name(self, student_name: str) -> Optional[dict]: + def fetch_student_data_by_name(self, student_name: str) -> dict | None: item_group = self.__get_item_group("students") for student_id in item_group: @@ -82,7 +82,7 @@ def fetch_student_data_by_name(self, student_name: str) -> Optional[dict]: student_data["id"] = student_id return student_data - def fetch_teacher_data_by_name(self, teacher_name: str) -> Optional[dict]: + def fetch_teacher_data_by_name(self, teacher_name: str) -> dict | None: item_group = self.__get_item_group("teachers") for teacher_id in item_group: @@ -91,7 +91,7 @@ def fetch_teacher_data_by_name(self, teacher_name: str) -> Optional[dict]: teacher_data["id"] = teacher_id return teacher_data - def fetch_parent_data_by_name(self, parent_name: str) -> Optional[dict]: + def fetch_parent_data_by_name(self, parent_name: str) -> dict | None: item_group = self.__get_item_group("parents") for parent_id in item_group: @@ -100,7 +100,7 @@ def fetch_parent_data_by_name(self, parent_name: str) -> Optional[dict]: parent_data["id"] = parent_id return parent_data - def fetch_person_data_by_name(self, name: str) -> Optional[dict]: + def fetch_person_data_by_name(self, name: str) -> dict | None: teacher_data = self.fetch_teacher_data_by_name(name) student_data = self.fetch_student_data_by_name(name) parent_data = self.fetch_parent_data_by_name(name) diff --git a/edupage_api/grades.py b/edupage_api/grades.py index 800bfd5..95f3a48 100644 --- a/edupage_api/grades.py +++ b/edupage_api/grades.py @@ -15,27 +15,27 @@ class EduGrade: event_id: int title: str - grade_n: Optional[Union[int, float, str]] - comment: Optional[str] + grade_n: int | float | str | None + comment: str | None date: datetime subject_id: int - subject_name: Optional[str] - teacher: Optional[EduTeacher] - max_points: Optional[float] - more_details: Optional[list[str]] + subject_name: str | None + teacher: EduTeacher | None + max_points: float | None + more_details: list[str] | None importance: float verbal: bool percent: float - class_grade_avg: Optional[float] + class_grade_avg: float | None @dataclass class EduTextGrade: grade_id: int - comment: Optional[str] + comment: str | None grade_type: str - date: Optional[datetime] + date: datetime | None subject_id: int - subject_name: Optional[str] + subject_name: str | None class Term(Enum): FIRST = "P1" @@ -69,7 +69,7 @@ def __get_grade_data_for_term(self, term: Term, year: int): raise FailedToParseGradeDataError("Failed to parse JSON") @ModuleHelper.logged_in - def get_text_grades(self, term: Optional[Term], year: Optional[int]) -> list[EduTextGrade]: + def get_text_grades(self, term: Term | None, year: int | None) -> list[EduTextGrade]: grade_data = ( self.__get_grade_data_for_term(term, year) if term and year @@ -111,7 +111,7 @@ def get_text_grades(self, term: Optional[Term], year: Optional[int]) -> list[Edu return grades @ModuleHelper.logged_in - def get_grades(self, term: Optional[Term], year: Optional[int]) -> list[EduGrade]: + def get_grades(self, term: Term | None, year: int | None) -> list[EduGrade]: grade_data = ( self.__get_grade_data_for_term(term, year) if term and year diff --git a/edupage_api/login.py b/edupage_api/login.py index 1b0b554..636a6be 100644 --- a/edupage_api/login.py +++ b/edupage_api/login.py @@ -102,7 +102,7 @@ def __login_with_rpc(self, username: str, password: str, subdomain: str): def login( self, username: str, password: str, subdomain: str = "login1" - ) -> Optional[TwoFactorLogin]: + ) -> TwoFactorLogin | None: """Login to your school's Edupage account (optionally with 2 factor authentication). If you do not have 2 factor authentication set up, this function will return `None`. diff --git a/edupage_api/login_session.py b/edupage_api/login_session.py index 4910869..1f13659 100644 --- a/edupage_api/login_session.py +++ b/edupage_api/login_session.py @@ -11,7 +11,7 @@ class LoginSession(Module): """Session handling shared by `Login` and `TwoFactorLogin`.""" @staticmethod - def parse_rpc_response(text: str) -> Optional[dict]: + def parse_rpc_response(text: str) -> dict | None: if not text: return None diff --git a/edupage_api/lunches.py b/edupage_api/lunches.py index 904ceb4..f55defc 100644 --- a/edupage_api/lunches.py +++ b/edupage_api/lunches.py @@ -1,7 +1,6 @@ import json from dataclasses import dataclass from datetime import date, datetime -from typing import List, Optional from enum import Enum from edupage_api.exceptions import ( @@ -53,7 +52,7 @@ class Menu: allergens: str weight: str number: str - rating: Optional[Rating] + rating: Rating | None class MealType(Enum): SNACK = 1 @@ -62,15 +61,15 @@ class MealType(Enum): @dataclass class Meal: - served_from: Optional[datetime] - served_to: Optional[datetime] + served_from: datetime | None + served_to: datetime | None amount_of_foods: int chooseable_menus: list[str] can_be_changed_until: datetime title: str - menus: List[Menu] + menus: list[Menu] date: datetime - ordered_meal: Optional[str] + ordered_meal: str | None meal_type: MealType __boarder_id: str __meal_index: str @@ -114,14 +113,14 @@ def sign_off(self, edupage: EdupageModule): @dataclass class Meals: - snack: Optional[Meal] - lunch: Optional[Meal] - afternoon_snack: Optional[Meal] + snack: Meal | None + lunch: Meal | None + afternoon_snack: Meal | None class Lunches(Module): - def parse_meal(self, meal_index: str, meal: dict, boarder_id: str, date: date) -> Optional[Meal]: + def parse_meal(self, meal_index: str, meal: dict, boarder_id: str, date: date) -> Meal | None: if meal is None: return None @@ -211,7 +210,7 @@ def parse_meal(self, meal_index: str, meal: dict, boarder_id: str, date: date) - ) @ModuleHelper.logged_in - def get_meals(self, date: date) -> Optional[Meals]: + def get_meals(self, date: date) -> Meals | None: date_strftime = date.strftime("%Y%m%d") request_url = f"https://{self.edupage.subdomain}.edupage.org/menu/?date={date_strftime}" response = self.edupage.session.get(request_url).content.decode() diff --git a/edupage_api/messages.py b/edupage_api/messages.py index 686ea65..0e2b2d2 100644 --- a/edupage_api/messages.py +++ b/edupage_api/messages.py @@ -9,7 +9,7 @@ class Messages(Module): def send_message( - self, recipients: Union[list[EduAccount], EduAccount, list[str]], body: str + self, recipients: list[EduAccount] | EduAccount | list[str], body: str ) -> int: recipient_string = "" diff --git a/edupage_api/module.py b/edupage_api/module.py index 2ba01b9..b8b38db 100644 --- a/edupage_api/module.py +++ b/edupage_api/module.py @@ -32,7 +32,7 @@ class ModuleHelper: # Helper Functions @staticmethod - def parse_int(val: str) -> Optional[int]: + def parse_int(val: str) -> int | None: try: return int("".join(filter(str.isdigit, val))) except ValueError: @@ -77,7 +77,7 @@ def encode_form_data(data: dict) -> str: return output @staticmethod - def strptime_or_none(date_string: str, format: str) -> Optional[datetime]: + def strptime_or_none(date_string: str, format: str) -> datetime | None: try: return datetime.strptime(date_string, format) except ValueError: diff --git a/edupage_api/parent.py b/edupage_api/parent.py index eea44bf..9f799ce 100644 --- a/edupage_api/parent.py +++ b/edupage_api/parent.py @@ -8,7 +8,7 @@ class Parent(Module): @ModuleHelper.logged_in @ModuleHelper.is_parent - def switch_to_child(self, child: Union[EduAccount, int]): + def switch_to_child(self, child: EduAccount | int): child_id = child.person_id if isinstance(child, EduAccount) else child params = {"studentid": child_id} diff --git a/edupage_api/people.py b/edupage_api/people.py index bfe76b8..69819b8 100644 --- a/edupage_api/people.py +++ b/edupage_api/people.py @@ -16,7 +16,7 @@ class Gender(str, Enum): FEMALE = "F" @staticmethod - def parse(gender_str: str) -> Optional[Gender]: + def parse(gender_str: str) -> Gender | None: return ModuleHelper.parse_enum(gender_str, Gender) @@ -31,7 +31,7 @@ class EduAccount: person_id: int name: str gender: Gender - in_school_since: Optional[datetime] + in_school_since: datetime | None account_type: EduAccountType @staticmethod @@ -46,7 +46,7 @@ def recognize_account_type(person_data: dict) -> EduAccountType: @staticmethod def parse( person_data: dict, person_id: int, edupage: EdupageModule - ) -> Optional[EduAccount]: + ) -> EduAccount | None: account_type = EduAccount.recognize_account_type(person_data) if account_type == EduAccountType.STUDENT: @@ -97,7 +97,7 @@ def __init__( person_id: int, name: str, gender: Gender, - in_school_since: Optional[datetime], + in_school_since: datetime | None, class_id: int, number_in_class: int, ): @@ -134,7 +134,7 @@ def __init__( person_id: int, name: str, gender: Gender, - in_school_since: Optional[datetime], + in_school_since: datetime | None, ): super().__init__( person_id, name, gender, in_school_since, EduAccountType.PARENT @@ -148,9 +148,9 @@ def __init__( person_id: int, name: str, gender: Gender, - in_school_since: Optional[datetime], + in_school_since: datetime | None, classroom_name: str, - teacher_to: Optional[datetime], + teacher_to: datetime | None, ): super().__init__( person_id, name, gender, in_school_since, EduAccountType.TEACHER @@ -162,7 +162,7 @@ def __init__( class People(Module): @ModuleHelper.logged_in - def get_students(self) -> Optional[list]: + def get_students(self) -> list | None: students = DbiHelper(self.edupage).fetch_student_list() if students is None: return None @@ -181,7 +181,7 @@ def get_students(self) -> Optional[list]: return result @ModuleHelper.logged_in - def get_all_students(self) -> Optional[list[EduStudent]]: + def get_all_students(self) -> list[EduStudent] | None: request_url = f"https://{self.edupage.subdomain}.edupage.org/rpr/server/maindbi.js?__func=mainDBIAccessor" data = { "__args": [ @@ -215,7 +215,7 @@ def get_all_students(self) -> Optional[list[EduStudent]]: return result @ModuleHelper.logged_in - def get_teacher(self, teacher_id: Union[int, str]) -> Optional[EduTeacher]: + def get_teacher(self, teacher_id: int | str) -> EduTeacher | None: try: teacher_id = int(teacher_id) except (ValueError, TypeError): @@ -231,7 +231,7 @@ def get_teacher(self, teacher_id: Union[int, str]) -> Optional[EduTeacher]: ) @ModuleHelper.logged_in - def get_student(self, student_id: Union[int, str]) -> Optional[EduStudent]: + def get_student(self, student_id: int | str) -> EduStudent | None: try: student_id = int(student_id) except (ValueError, TypeError): @@ -247,7 +247,7 @@ def get_student(self, student_id: Union[int, str]) -> Optional[EduStudent]: ) @ModuleHelper.logged_in - def get_teachers(self) -> Optional[list]: + def get_teachers(self) -> list | None: teachers = DbiHelper(self.edupage).fetch_teacher_list() if teachers is None: return None diff --git a/edupage_api/py.typed b/edupage_api/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/edupage_api/subjects.py b/edupage_api/subjects.py index 9c002ce..a357d97 100644 --- a/edupage_api/subjects.py +++ b/edupage_api/subjects.py @@ -14,7 +14,7 @@ class Subject: class Subjects(Module): @ModuleHelper.logged_in - def get_subjects(self) -> Optional[list]: + def get_subjects(self) -> list | None: subject_list = DbiHelper(self.edupage).fetch_subject_list() if subject_list is None: @@ -36,7 +36,7 @@ def get_subjects(self) -> Optional[list]: return subjects - def get_subject(self, subject_id: Union[int, str]) -> Optional[Subject]: + def get_subject(self, subject_id: int | str) -> Subject | None: try: subject_id = int(subject_id) except (ValueError, TypeError): diff --git a/edupage_api/substitution.py b/edupage_api/substitution.py index 073ac2b..40d7ab4 100644 --- a/edupage_api/substitution.py +++ b/edupage_api/substitution.py @@ -18,7 +18,7 @@ class Action(str, Enum): DELETION = "remove" @staticmethod - def parse(string: str) -> Optional[Action]: + def parse(string: str) -> Action | None: return ModuleHelper.parse_enum(string, Action) @@ -27,7 +27,7 @@ class TimetableChange: change_class: str lesson_n: int title: str - action: Union[Action, tuple[int, int]] + action: Action | tuple[int, int] class Substitution(Module): @@ -53,7 +53,7 @@ def __get_substitution_data(self, date: date) -> str: return response.get("r") @ModuleHelper.logged_in - def get_missing_teachers(self, date: date) -> Optional[list[EduTeacher]]: + def get_missing_teachers(self, date: date) -> list[EduTeacher] | None: html = self.__get_substitution_data(date) missing_teachers_string = html.split('')[ 1 @@ -95,7 +95,7 @@ def get_missing_teachers(self, date: date) -> Optional[list[EduTeacher]]: return missing_teachers @ModuleHelper.logged_in - def get_timetable_changes(self, date: date) -> Optional[list[TimetableChange]]: + def get_timetable_changes(self, date: date) -> list[TimetableChange] | None: html = self.__get_substitution_data(date) class_delim = ( diff --git a/edupage_api/timeline.py b/edupage_api/timeline.py index f1c75e7..68a4b0a 100644 --- a/edupage_api/timeline.py +++ b/edupage_api/timeline.py @@ -154,7 +154,7 @@ class EventType(str, Enum): H_USERPHOTO = "h_userphoto" @staticmethod - def parse(event_type_str: str) -> Optional[EventType]: + def parse(event_type_str: str) -> EventType | None: return ModuleHelper.parse_enum( event_type_str, EventType # pyright: ignore[reportArgumentType] ) @@ -165,32 +165,32 @@ class TimelineEvent: event_id: int timestamp: datetime text: str - author: Union[EduAccount, str] - recipient: Union[EduAccount, str] + author: EduAccount | str + recipient: EduAccount | str event_type: EventType additional_data: dict is_done: bool = False - done_at: Optional[datetime] = None + done_at: datetime | None = None is_starred: bool = False reaction_count: int = 0 - created_at: Optional[datetime] = None + created_at: datetime | None = None is_removed: bool = False # Meaning depends on `event_type`, e.g. chat_id for EventType.CHAT. # An int when the value is numeric, otherwise the raw str (e.g. hex IDs, dates). - other_id: Optional[Union[int, str]] = None + other_id: int | str | None = None # event_id of the event this one replies to, # e.g. a reply to an EventType.MESSAGE points to the original message's event_id. - response_to: Optional[int] = None + response_to: int | None = None # Meaning depends on `event_type`, e.g. the deadline day for EventType.HOMEWORK. - event_time: Optional[datetime] = None + event_time: datetime | None = None class TimelineEvents(Module): def __parse_items( - self, timeline_items: dict, user_props: Optional[dict] = None + self, timeline_items: dict, user_props: dict | None = None ) -> list[TimelineEvent]: output = [] diff --git a/edupage_api/timetables.py b/edupage_api/timetables.py index c950dd3..b57ba32 100644 --- a/edupage_api/timetables.py +++ b/edupage_api/timetables.py @@ -1,7 +1,6 @@ import json from dataclasses import dataclass from datetime import date, datetime, time -from typing import List, Optional, Union from edupage_api.classes import Class, Classes from edupage_api.classrooms import Classroom, Classrooms @@ -19,17 +18,17 @@ @dataclass class Lesson: - period: Optional[int] + period: int | None start_time: time end_time: time duration: int - subject: Optional[Subject] - classes: Optional[List[Class]] - groups: Optional[List[str]] - teachers: Optional[List[EduTeacher]] - classrooms: Optional[List[Classroom]] - curriculum: Optional[str] - online_lesson_link: Optional[str] + subject: Subject | None + classes: list[Class] | None + groups: list[str] | None + teachers: list[EduTeacher] | None + classrooms: list[Classroom] | None + curriculum: str | None + online_lesson_link: str | None is_cancelled: bool is_event: bool @@ -65,7 +64,7 @@ def sign_into_lesson(self, edupage: EdupageModule): @dataclass class Timetable: - lessons: List[Lesson] + lessons: list[Lesson] def __iter__(self): return iter(self.lessons) @@ -284,7 +283,7 @@ def __parse_timetable(self, plan): return Timetable(lessons) @ModuleHelper.logged_in - def get_my_timetable(self, date: date) -> Optional[Timetable]: + def get_my_timetable(self, date: date) -> Timetable | None: selected_child_id = self.edupage._selected_child_id if selected_child_id is not None: @@ -303,9 +302,9 @@ def get_my_timetable(self, date: date) -> Optional[Timetable]: @ModuleHelper.logged_in def get_timetable( self, - target: Union[EduTeacher, EduStudent, Class, Classroom], + target: EduTeacher | EduStudent | Class | Classroom, date: date, - ) -> Optional[Timetable]: + ) -> Timetable | None: lookup = { EduTeacher: ("teachers", "person_id"), diff --git a/edupage_api/twofactor.py b/edupage_api/twofactor.py index e3be8f4..95e45a5 100644 --- a/edupage_api/twofactor.py +++ b/edupage_api/twofactor.py @@ -17,20 +17,20 @@ @dataclass class TwoFactorLogin: - __authentication_endpoint: Optional[str] - __authentication_token: Optional[str] - __csrf_token: Optional[str] + __authentication_endpoint: str | None + __authentication_token: str | None + __csrf_token: str | None __edupage: EdupageModule __use_modern_rpc: bool = False - __code: Optional[str] = None + __code: str | None = None # Only provided by modern RPC 2FA page - email: Optional[str] = None - device_names: Optional[list[str]] = None + email: str | None = None + device_names: list[str] | None = None @staticmethod - def __extract_form_fields(page: str) -> Optional[dict]: + def __extract_form_fields(page: str) -> dict | None: try: return { "csrfauth": page.split('csrfauth" value="', 1)[1].split('"', 1)[0], diff --git a/pyproject.toml b/pyproject.toml index b5a3c46..178b037 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,43 @@ [build-system] requires = [ - "setuptools>=42", - "wheel" + "setuptools>=77", + "setuptools-scm>=8" ] -build-backend = "setuptools.build_meta" \ No newline at end of file +build-backend = "setuptools.build_meta" + +[project] +name = "edupage_api" +dynamic = ["version"] +description = "A Python library for accesing your Edupage account" +readme = "README.md" +requires-python = ">=3.10" +license = "GPL-3.0-or-later" +license-files = ["LICENSE"] +authors = [{ name = "ivanhrabcak", email = "ivan@hrabcak.eu" }] +keywords = ["edupage", "edupage api", "library", "api", "school"] +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Education", + "Topic :: Software Development :: Libraries", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Typing :: Typed", +] +dependencies = ["requests>=2.28"] + +[project.urls] +Homepage = "https://github.com/EdupageAPI/edupage-api" +Documentation = "https://edupageapi.github.io/edupage-api" +Issues = "https://github.com/EdupageAPI/edupage-api/issues" + +[dependency-groups] +dev = ["build", "pdoc", "ruff"] + +[tool.setuptools.packages.find] +include = ["edupage_api*"] + +[tool.setuptools_scm] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 17b1fef..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -requests -datetime -requests[security] -colorama \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 7630d48..0000000 --- a/setup.cfg +++ /dev/null @@ -1,24 +0,0 @@ -[metadata] -name = edupage_api -version = 0.13.0 -description = A Python library for accessing your EduPage account -long_description = file: README.md -long_description_content_type = text/markdown -author = ivanhrabcak -author_email = ivan@hrabcak.eu -keywords = edupage, edupage api, library -url = https://github.com/EdupageAPI/edupage-api -classifiers = - Development Status :: 3 - Alpha - Intended Audience :: Developers - Topic :: Software Development :: Build Tools - License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - -[options] -packages = find: -python_requires = >=3.9 -install_requires = requests diff --git a/setup.py b/setup.py deleted file mode 100644 index 94615fd..0000000 --- a/setup.py +++ /dev/null @@ -1,8 +0,0 @@ -import sys - -import setuptools - -if sys.version_info < (3, 9): - raise RuntimeError("This package requres Python 3.9+") - -setuptools.setup()