Skip to content

Commit f17d434

Browse files
authored
refactor: Major cleanup and code modernization (#10)
* Using logger instead of print * Adding new script * Fix if no proper FutureTask received * A bit cleaner * Adding run * Improving by moving everything into the cache * Adding PlanningElements * New TODO list based on planner * WIP * Poetry update * ruff reformat * Correcting failing test * Correcting failing test * sonar hint * sonar hint2
1 parent d0f331a commit f17d434

25 files changed

+4362
-335
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
scripts/list_all_courses text eol=lf
2+
scripts/smartschool_report_on_future_tasks text eol=lf
13
scripts/smartschool_report_on_results text eol=lf
24
*.py text eol=lf

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
dist/
55
.cache/
66
__pycache__/
7+
logs/
78

89
*.iml
910
credentials.yml
1011
cookies.txt
1112
.coverage
1213
coverage.json
14+
*.log

poetry.lock

Lines changed: 244 additions & 223 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "smartschool"
33
version = "0.5.0"
44
description = "Unofficial API interface to the smartschool system."
5-
authors = ["Steven 'KaReL' Van Ingelgem <[email protected]>"]
5+
authors = ["Steven Van Ingelgem <[email protected]>"]
66
readme = "README.md"
77
homepage = "https://github.com/svaningelgem/smartschool"
88
license = "GPL-3.0"

run

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export PYTHON="/opt/miniforge3/envs/smartschool/bin/python"
1818
echo "RESULTS-REPORT" |& tee -a "$logfile"
1919
$PYTHON scripts/smartschool_report_on_results |& tee -a "$logfile"
2020

21-
echo "FUTURE-TASKS-REPORT" |& tee -a "$logfile"
22-
$PYTHON scripts/smartschool_report_on_future_tasks |& tee -a "$logfile"
21+
# echo "FUTURE-TASKS-REPORT" |& tee -a "$logfile"
22+
# $PYTHON scripts/smartschool_report_on_future_tasks |& tee -a "$logfile"
2323

2424
) 200>/var/lock/smartschool.lock

scripts/_common_tasks_stuff.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from __future__ import annotations
2+
3+
from datetime import datetime, timedelta
4+
from functools import lru_cache
5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from datetime import date, time
9+
10+
11+
def _email_text(hour_start: datetime | time, hour_end: datetime | time, course_name: str, task_label: str, description: str) -> str:
12+
return f"date: {hour_start:%Y-%m-%d}\ntime: {hour_start:%H:%M} -> {hour_end:%H:%M}\nles: {course_name}\n\n{task_label}:\n {description}\n"
13+
14+
15+
@lru_cache(1)
16+
def _next_weekday() -> date | None:
17+
now = datetime.now()
18+
19+
if (now.hour in [12, 20] and now.isoweekday() == 3) or (now.hour in [17, 20] and now.isoweekday() != 3): # Wednesday
20+
... # Ok
21+
else:
22+
return None
23+
24+
next_weekday = now + timedelta(days=1)
25+
while next_weekday.isoweekday() in [6, 7]: # Saturday, Sunday
26+
next_weekday += timedelta(days=1)
27+
28+
return next_weekday.date()

scripts/list_all_courses

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env python
2+
from logprise import logger
3+
4+
from smartschool import Smartschool, PathCredentials, Courses
5+
6+
Smartschool.start(PathCredentials())
7+
for course in Courses():
8+
logger.info(course.name)

scripts/smartschool_report_on_future_tasks

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ from datetime import date, datetime, timedelta
55
from smartschool import Courses, FutureTasks, PathCredentials, Periods, Smartschool, SmartschoolHours, SmartschoolLessons, SmartschoolMomentInfos
66
from smartschool.common import IsSaved, save, send_email
77
from smartschool.objects import AgendaHour, FutureTaskOneTask
8-
9-
session = Smartschool.start(PathCredentials())
10-
assert 'email_from' in session.creds.other_info
11-
assert 'email_to' in session.creds.other_info
8+
from smartschool.session import session
129

1310

1411
def _is_equal_task(a: FutureTaskOneTask, b: FutureTaskOneTask) -> bool:
@@ -100,6 +97,10 @@ def report_tasks_for_next_day(all_tasks: dict):
10097
def main():
10198
all_tasks: dict[date, dict[str, list]] = defaultdict(lambda: defaultdict(list))
10299

100+
Smartschool.start(PathCredentials())
101+
assert 'email_from' in session.creds.other_info
102+
assert 'email_to' in session.creds.other_info
103+
103104
for day in FutureTasks().days:
104105
for course in day.courses:
105106
assert not course.items.materials, "Please correct the model to include this information."
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python
2+
from collections import defaultdict
3+
from datetime import date
4+
5+
from _common_tasks_stuff import _next_weekday, _email_text
6+
from smartschool import PathCredentials, Smartschool
7+
from smartschool.common import IsSaved, save, send_email
8+
from smartschool.objects import FutureTaskOneTask, PlannedElement
9+
from smartschool.planner import PlannedElements
10+
from smartschool.session import session
11+
12+
13+
def _is_equal_task(a: PlannedElement, b: PlannedElement) -> bool:
14+
return (
15+
[c.name for c in a.courses],
16+
a.period.dateTimeFrom,
17+
a.period.dateTimeTo,
18+
a.assignmentType.name,
19+
a.name
20+
) == (
21+
[c.name for c in b.courses],
22+
b.period.dateTimeFrom,
23+
b.period.dateTimeTo,
24+
b.assignmentType.name,
25+
b.name
26+
)
27+
28+
29+
def process_task(task: PlannedElement, all_tasks: dict) -> None:
30+
"""Stores in an array + report if it's a new/updated task."""
31+
next_weekday = _next_weekday()
32+
is_for_next_day = next_weekday < task.period.dateTimeFrom.date() or next_weekday > task.period.dateTimeTo.date()
33+
34+
course_name: str = task.courses[0].name
35+
hour: str = task.period.dateTimeFrom.strftime("%H:%M")
36+
when: str = task.period.dateTimeFrom.strftime("%Y-%m-%d")
37+
38+
text = _email_text(
39+
hour_start=task.period.dateTimeFrom,
40+
hour_end=task.period.dateTimeTo,
41+
course_name=course_name,
42+
task_label=task.assignmentType.name,
43+
description=task.name,
44+
)
45+
46+
if is_for_next_day:
47+
all_tasks[when][hour].append(text)
48+
49+
status = save(type_="todo", course_name=course_name, id_=task.assignmentType.id, data=task, is_eq=_is_equal_task)
50+
if status == IsSaved.SAME:
51+
return
52+
53+
subject = f"📑 [{when} {hour}] {course_name} ({task.name})"
54+
if status != IsSaved.NEW:
55+
subject = f"⚠⚠ {subject}" # There is an update...
56+
57+
send_email(subject=subject, text=text, email_from=session.creds.other_info['email_from'],
58+
email_to=session.creds.other_info['email_to'])
59+
60+
61+
def report_tasks_for_next_day(all_tasks: dict):
62+
for dag, rest in all_tasks.items():
63+
text = []
64+
65+
for _, tasks in sorted(rest.items(), key=lambda x: x[0]): # type: str, list[PlannedElement]
66+
for task in tasks:
67+
text.append(task)
68+
69+
text = "\n---------------------------------------\n\n".join(text)
70+
71+
status = save('todo', '.email', dag, data=text, extension='txt')
72+
if status == IsSaved.SAME:
73+
continue # Don't re-send if nothing changed
74+
75+
subject = f"🍕 Todo list {dag}"
76+
if status != IsSaved.NEW:
77+
subject = f"⚠⚠ {subject}" # There is an update...
78+
79+
send_email(
80+
subject=subject,
81+
text=text, email_from=session.creds.other_info['email_from'], email_to=session.creds.other_info['email_to'])
82+
83+
84+
def main():
85+
all_tasks: dict[date, dict[str, list]] = defaultdict(lambda: defaultdict(list))
86+
87+
Smartschool.start(PathCredentials())
88+
assert 'email_from' in session.creds.other_info
89+
assert 'email_to' in session.creds.other_info
90+
91+
for element in PlannedElements():
92+
process_task(element, all_tasks)
93+
94+
report_tasks_for_next_day(all_tasks)
95+
96+
97+
if __name__ == '__main__':
98+
main()

scripts/smartschool_report_on_results

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
from logprise import logger
44
from smartschool import PathCredentials, Results, Smartschool, DownloadError
55
from smartschool.common import IsSaved, save, send_email
6-
from smartschool.objects import Result
6+
from smartschool.objects import ResultWithoutDetails
77
from smartschool.session import session
88

99

10-
def is_punten_json_the_same(previous: Result, current: Result) -> bool:
10+
def is_punten_json_the_same(previous: ResultWithoutDetails, current: ResultWithoutDetails) -> bool:
1111
if previous == current: # No need to dig into it...
1212
return True
1313

@@ -33,15 +33,15 @@ def is_punten_json_the_same(previous: Result, current: Result) -> bool:
3333
"feedback/5/text",
3434
]
3535

36-
def _grab_sub(d: Result | dict, key: list[str]) -> dict | list | str | None:
37-
if not key or d is None:
36+
def _grab_sub(d: ResultWithoutDetails | dict, k: list[str]) -> dict | list | str | None:
37+
if not k or d is None:
3838
return d
3939

4040
obj = d
41-
for piece in key:
41+
for piece in k:
4242
if piece.isnumeric():
4343
piece = int(piece)
44-
if piece >= len(obj): # Not enough inside this array >> Just assume it's an empty string
44+
if piece >= len(obj): # Not enough inside this array >> Assume it's an empty string
4545
return ""
4646

4747
obj = obj[piece]
@@ -58,17 +58,17 @@ def is_punten_json_the_same(previous: Result, current: Result) -> bool:
5858
if prev != curr:
5959
return False
6060
except Exception as ex:
61-
print(f"Exception received: {ex.__class__.__name__} ({ex})")
62-
print("Current: ", current)
63-
print("Previous: ", previous)
61+
logger.error("Current: ", current)
62+
logger.error("Previous: ", previous)
63+
logger.exception(ex)
6464
raise
6565

6666
return True
6767

6868

6969
def build_text(
7070
is_update: bool,
71-
result: Result,
71+
result: ResultWithoutDetails,
7272
) -> tuple[str, str]:
7373
course_name = result.courses[0].name
7474
teacher_names = [teacher.name.startingWithFirstName for teacher in result.courses[0].teachers]
@@ -110,29 +110,29 @@ def build_text(
110110
0.5: "🌫",
111111
}
112112

113+
icon = "😡"
113114
for min_pct, symbol in limits.items():
114115
if result.graphic.percentage >= min_pct:
115116
icon = symbol
116117
break
117-
else:
118-
icon = "😡"
119118

120119
email_subject = f"{icon} {email_subject}"
121120

122121
return text, email_subject
123122

124123

125-
def process_result(result: Result) -> None:
126-
logger.info("Processing {}", result.name)
127-
124+
def process_result(result: ResultWithoutDetails) -> None:
128125
assert len(result.courses) == 1, f"Multiple courses? {result.courses}"
129126

127+
logger.info("[{}] Processing '{}'", result.courses[0].name, result.name)
128+
130129
course_name = result.courses[0].name # FE: 'Frans'
131130
id_ = result.identifier
132131

133132
status = save("punten", course_name, id_, result, is_punten_json_the_same)
134133

135134
if status == IsSaved.SAME:
135+
logger.debug(" => Already processed")
136136
return
137137

138138
text, subject = build_text(result=result, is_update=status != IsSaved.NEW)

0 commit comments

Comments
 (0)