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(" ", "").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(" ", "") 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
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.
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(" ", "").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(" ", "") 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.