librus_apix.timetable

Module: timetable_parser

Description: This module provides functions for parsing and retrieving timetable data from an educational institution's website.

Classes: - Period: Represents a period of a class with relevant information.

Functions: - get_timetable: Retrieves the timetable for a given week starting from a Monday date.

Exceptions: - DateError: Raised when the provided date is not a Monday. - ParseError: Raised when there's an error while parsing the timetable.

Usage:

from your_client_module import Client  # import your client module here

# Example usage:
client = Client()  # initialize your client
monday_date = datetime(2024, 5, 13)  # example Monday date
try:
    timetable = get_timetable(client, monday_date)
    for day in timetable:
        for period in day:
            print(period.subject, period.date, period.date_from, period.date_to)
except DateError as e:
    print(e)
except ParseError as e:
    print(e)
  1"""
  2Module: timetable_parser
  3
  4Description:
  5This module provides functions for parsing and retrieving timetable data from an educational institution's website.
  6
  7Classes:
  8    - Period: Represents a period of a class with relevant information.
  9
 10Functions:
 11    - get_timetable: Retrieves the timetable for a given week starting from a Monday date.
 12
 13Exceptions:
 14    - DateError: Raised when the provided date is not a Monday.
 15    - ParseError: Raised when there's an error while parsing the timetable.
 16
 17Usage:
 18```python
 19from your_client_module import Client  # import your client module here
 20
 21# Example usage:
 22client = Client()  # initialize your client
 23monday_date = datetime(2024, 5, 13)  # example Monday date
 24try:
 25    timetable = get_timetable(client, monday_date)
 26    for day in timetable:
 27        for period in day:
 28            print(period.subject, period.date, period.date_from, period.date_to)
 29except DateError as e:
 30    print(e)
 31except ParseError as e:
 32    print(e)
 33```
 34"""
 35
 36from typing import List, Dict
 37from librus_apix.client import Client
 38from librus_apix.exceptions import ParseError, DateError
 39from librus_apix.helpers import no_access_check
 40from datetime import datetime, timedelta
 41from bs4 import BeautifulSoup
 42from dataclasses import dataclass
 43
 44
 45@dataclass
 46class Period:
 47    """
 48    Represents a period of a class with relevant information.
 49
 50    Attributes:
 51        subject (str): The subject of the class.
 52        teacher_and_classroom (str): Combined information of teacher and classroom.
 53        date (str): The date of the period.
 54        date_from (str): Starting time of the period.
 55        date_to (str): Ending time of the period.
 56        weekday (str): The day of the week of the period.
 57        info (Dict[str, str]): Additional information about the period.
 58        number (int): The number of the period within a day.
 59        next_recess_from (str | None): Starting time of the next recess, if any.
 60        next_recess_to (str | None): Ending time of the next recess, if any.
 61    """
 62
 63    subject: str
 64    teacher_and_classroom: str
 65    date: str
 66    date_from: str
 67    date_to: str
 68    weekday: str
 69    info: Dict[str, str]
 70    number: int
 71    next_recess_from: str | None
 72    next_recess_to: str | None
 73
 74
 75def get_timetable(client: Client, monday_date: datetime) -> List[List[Period]]:
 76    """
 77    Retrieves the timetable for a given week starting from a Monday date.
 78
 79    Args:
 80        client (Client): An instance of the client class for fetching data.
 81        monday_date (datetime): The Monday date for the week's timetable.
 82
 83    Returns:
 84        List[List[Period]]: A nested list containing periods for each day of the week.
 85
 86    Raises:
 87        DateError: If the provided date is not a Monday.
 88        ParseError: If there's an error while parsing the timetable.
 89    """
 90    timetable: List[List[Period]] = []
 91    if monday_date.strftime("%A") != "Monday":
 92        raise DateError("You must input a Monday date.")
 93    sunday = monday_date + timedelta(days=6)
 94    week = f"{monday_date.strftime('%Y-%m-%d')}_{sunday.strftime('%Y-%m-%d')}"
 95    post = client.post(client.TIMETABLE_URL, data={"tydzien": week})
 96    soup = no_access_check(BeautifulSoup(post.text, "lxml"))
 97    periods = soup.select("table.decorated.plan-lekcji > tr.line1")
 98    if len(periods) < 1:
 99        raise ParseError("Error in parsing timetable.")
100    recess = soup.select("table.decorated.plan-lekcji > tr.line0")
101    for weekday in range(7):
102        timetable.append([])
103        for period in range(len(periods)):
104            [recess_from, recess_to] = [None, None]
105            if period <= len(recess) - 1:
106                center = recess[period].select_one("td.center")
107                if center is None:
108                    raise ParseError("Error while parsing timetable (center)")
109                [recess_from, recess_to] = [
110                    x.strip()
111                    for x in center.text.replace("&nbsp;", "").strip().split("-", 1)
112                ]
113            lesson = periods[period].select(
114                'td[id="timetableEntryBox"][class="line1"]'
115            )[weekday]
116            td_center = periods[period].select_one('td[class="center"]')
117            if td_center is None:
118                raise ParseError("Error while parsing lesson_number of period")
119            lesson_number = int(td_center.text)
120            tooltip = lesson.select_one("div.center.plan-lekcji-info")
121            a_href = lesson.select_one("a")
122            info = {}
123            if tooltip is not None:
124                if a_href is None:
125                    info[tooltip.text.strip()] = ""
126                else:
127                    attr_dict = {}
128                    for attr in (
129                        a_href.attrs["title"]
130                        .replace("<b>", "")
131                        .replace("</b>", "")
132                        .replace("\xa0", " ")
133                        .split("<br>")
134                    ):
135                        if len(attr.strip()) > 2:
136                            key, value = attr.split(": ", 1)
137                            attr_dict[key] = value
138
139                    info[tooltip.text.strip()] = {
140                        "teacher_swap": attr_dict.get("Nauczyciel", ""),
141                        "subject_swap": attr_dict.get("Przedmiot", ""),
142                        "classroom_swap": attr_dict.get("Sala", ""),
143                        "date_added": attr_dict.get("Data dodania", ""),
144                    }
145            date, date_from, date_to = [
146                val for key, val in lesson.attrs.items() if key.startswith("data")
147            ]
148            lesson = lesson.select_one("div.text")
149            if lesson is None:
150                subject = ""
151                teacher_and_classroom = ""
152            else:
153                subject = lesson.select_one("b")
154                subject = subject.text if subject is not None else ""
155                teacher_and_classroom = (
156                    lesson.text.replace("\xa0", " ")
157                    .replace("\n", "")
158                    .replace("&nbsp", "")
159                    .split("-")
160                )
161                if len(teacher_and_classroom) >= 2:
162                    teacher_and_classroom = "-".join(teacher_and_classroom[1:])
163                else:
164                    teacher_and_classroom = ""
165
166            weekday_str = datetime.strptime(date, "%Y-%m-%d").strftime("%A")
167            p = Period(
168                subject,
169                teacher_and_classroom,
170                date,
171                date_from,
172                date_to,
173                weekday_str,
174                info,
175                lesson_number,
176                recess_from,
177                recess_to,
178            )
179            timetable[weekday].append(p)
180    return timetable
@dataclass
class Period:
46@dataclass
47class Period:
48    """
49    Represents a period of a class with relevant information.
50
51    Attributes:
52        subject (str): The subject of the class.
53        teacher_and_classroom (str): Combined information of teacher and classroom.
54        date (str): The date of the period.
55        date_from (str): Starting time of the period.
56        date_to (str): Ending time of the period.
57        weekday (str): The day of the week of the period.
58        info (Dict[str, str]): Additional information about the period.
59        number (int): The number of the period within a day.
60        next_recess_from (str | None): Starting time of the next recess, if any.
61        next_recess_to (str | None): Ending time of the next recess, if any.
62    """
63
64    subject: str
65    teacher_and_classroom: str
66    date: str
67    date_from: str
68    date_to: str
69    weekday: str
70    info: Dict[str, str]
71    number: int
72    next_recess_from: str | None
73    next_recess_to: str | None

Represents a period of a class with relevant information.

Attributes: subject (str): The subject of the class. teacher_and_classroom (str): Combined information of teacher and classroom. date (str): The date of the period. date_from (str): Starting time of the period. date_to (str): Ending time of the period. weekday (str): The day of the week of the period. info (Dict[str, str]): Additional information about the period. number (int): The number of the period within a day. next_recess_from (str | None): Starting time of the next recess, if any. next_recess_to (str | None): Ending time of the next recess, if any.

Period( subject: str, teacher_and_classroom: str, date: str, date_from: str, date_to: str, weekday: str, info: Dict[str, str], number: int, next_recess_from: str | None, next_recess_to: str | None)
subject: str
teacher_and_classroom: str
date: str
date_from: str
date_to: str
weekday: str
info: Dict[str, str]
number: int
next_recess_from: str | None
next_recess_to: str | None
def get_timetable( client: librus_apix.client.Client, monday_date: datetime.datetime) -> List[List[Period]]:
 76def get_timetable(client: Client, monday_date: datetime) -> List[List[Period]]:
 77    """
 78    Retrieves the timetable for a given week starting from a Monday date.
 79
 80    Args:
 81        client (Client): An instance of the client class for fetching data.
 82        monday_date (datetime): The Monday date for the week's timetable.
 83
 84    Returns:
 85        List[List[Period]]: A nested list containing periods for each day of the week.
 86
 87    Raises:
 88        DateError: If the provided date is not a Monday.
 89        ParseError: If there's an error while parsing the timetable.
 90    """
 91    timetable: List[List[Period]] = []
 92    if monday_date.strftime("%A") != "Monday":
 93        raise DateError("You must input a Monday date.")
 94    sunday = monday_date + timedelta(days=6)
 95    week = f"{monday_date.strftime('%Y-%m-%d')}_{sunday.strftime('%Y-%m-%d')}"
 96    post = client.post(client.TIMETABLE_URL, data={"tydzien": week})
 97    soup = no_access_check(BeautifulSoup(post.text, "lxml"))
 98    periods = soup.select("table.decorated.plan-lekcji > tr.line1")
 99    if len(periods) < 1:
100        raise ParseError("Error in parsing timetable.")
101    recess = soup.select("table.decorated.plan-lekcji > tr.line0")
102    for weekday in range(7):
103        timetable.append([])
104        for period in range(len(periods)):
105            [recess_from, recess_to] = [None, None]
106            if period <= len(recess) - 1:
107                center = recess[period].select_one("td.center")
108                if center is None:
109                    raise ParseError("Error while parsing timetable (center)")
110                [recess_from, recess_to] = [
111                    x.strip()
112                    for x in center.text.replace("&nbsp;", "").strip().split("-", 1)
113                ]
114            lesson = periods[period].select(
115                'td[id="timetableEntryBox"][class="line1"]'
116            )[weekday]
117            td_center = periods[period].select_one('td[class="center"]')
118            if td_center is None:
119                raise ParseError("Error while parsing lesson_number of period")
120            lesson_number = int(td_center.text)
121            tooltip = lesson.select_one("div.center.plan-lekcji-info")
122            a_href = lesson.select_one("a")
123            info = {}
124            if tooltip is not None:
125                if a_href is None:
126                    info[tooltip.text.strip()] = ""
127                else:
128                    attr_dict = {}
129                    for attr in (
130                        a_href.attrs["title"]
131                        .replace("<b>", "")
132                        .replace("</b>", "")
133                        .replace("\xa0", " ")
134                        .split("<br>")
135                    ):
136                        if len(attr.strip()) > 2:
137                            key, value = attr.split(": ", 1)
138                            attr_dict[key] = value
139
140                    info[tooltip.text.strip()] = {
141                        "teacher_swap": attr_dict.get("Nauczyciel", ""),
142                        "subject_swap": attr_dict.get("Przedmiot", ""),
143                        "classroom_swap": attr_dict.get("Sala", ""),
144                        "date_added": attr_dict.get("Data dodania", ""),
145                    }
146            date, date_from, date_to = [
147                val for key, val in lesson.attrs.items() if key.startswith("data")
148            ]
149            lesson = lesson.select_one("div.text")
150            if lesson is None:
151                subject = ""
152                teacher_and_classroom = ""
153            else:
154                subject = lesson.select_one("b")
155                subject = subject.text if subject is not None else ""
156                teacher_and_classroom = (
157                    lesson.text.replace("\xa0", " ")
158                    .replace("\n", "")
159                    .replace("&nbsp", "")
160                    .split("-")
161                )
162                if len(teacher_and_classroom) >= 2:
163                    teacher_and_classroom = "-".join(teacher_and_classroom[1:])
164                else:
165                    teacher_and_classroom = ""
166
167            weekday_str = datetime.strptime(date, "%Y-%m-%d").strftime("%A")
168            p = Period(
169                subject,
170                teacher_and_classroom,
171                date,
172                date_from,
173                date_to,
174                weekday_str,
175                info,
176                lesson_number,
177                recess_from,
178                recess_to,
179            )
180            timetable[weekday].append(p)
181    return timetable

Retrieves the timetable for a given week starting from a Monday date.

Args: client (Client): An instance of the client class for fetching data. monday_date (datetime): The Monday date for the week's timetable.

Returns: List[List[Period]]: A nested list containing periods for each day of the week.

Raises: DateError: If the provided date is not a Monday. ParseError: If there's an error while parsing the timetable.