Skip to content
This repository was archived by the owner on Jan 20, 2026. It is now read-only.

Commit efa3f73

Browse files
author
kurwjan
committed
⚡ feat: Added option to use school name and location
1 parent c46f06f commit efa3f73

4 files changed

Lines changed: 100 additions & 19 deletions

File tree

docs/source/api.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ General functions
2121

2222
.. autofunction:: close
2323

24+
Get all schools
25+
~~~~~~~~~~~~~~~~~~~
26+
27+
Functions
28+
^^^^^^^^^
29+
30+
.. autofunction:: get_schools
31+
32+
Types
33+
^^^^^
34+
35+
.. autoclass:: School
36+
2437
Substitution plan
2538
~~~~~~~~~~~~~~~~~
2639

docs/source/first_steps.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Example code
4040
4141
def main():
4242
client = LanisClient("schoolid", "name.lastname", "password")
43+
or: client = LanisClient(LanisClient.School("Testschule MH", "Testhausen City"), "password")
4344
client.authenticate()
4445
print(client.get_substitution_plan())
4546
client.close()
@@ -48,6 +49,7 @@ Example code
4849
main()
4950
5051
1. First you initialise the ``LanisClient`` class with the ``schoolid`` you can find it in the url at ``?=i`` in https://start.schulportal.hessen.de/?i=SCHOOLID.
51-
2. Then you log in with ``authenticate()``.
52-
3. Then we print the current substitution plan.
53-
4. Then we close the client. **You need to do this.**
52+
2. Or you initalise it with School(``school``, ``city``).
53+
3. Then you log in with ``authenticate()``.
54+
4. Then we print the current substitution plan.
55+
5. Then we close the client. **You need to do this.**

src/lanisapi/lanisapi.py

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import re
99
from datetime import datetime, date
1010
import calendar
11+
import json
12+
import os.path
1113

1214
from .authentication_functions import get_authentication_data, get_authentication_url, get_session
1315

@@ -19,12 +21,15 @@ class LanisClient:
1921
2022
Parameters
2123
----------
22-
schoolid : str
23-
The id of the school which you can see it in the url at ``i=``.
24+
school : str | School
25+
1. The id of the school which you can see it in the url at ``i=``.
26+
2. The school name and city in ``School``.
2427
username : str
2528
The username in firstname.lastname.
2629
password : str
2730
The password.
31+
save : bool, default True
32+
If False the school list and future things won't be saved to a file.
2833
ad_header : httpx.Headers, default {"user-agent": ....}
2934
Send custom headers to Lanis. Primarily used to send a
3035
custom ``user-agent``.
@@ -134,6 +139,21 @@ class CalendarData:
134139
end: datetime
135140
data: list[CalendarData] = None
136141
json: list[dict[str, any]] = None
142+
143+
@dataclass
144+
class School:
145+
"""
146+
Alternative to school id for authentication.
147+
148+
Parameters
149+
----------
150+
name : str
151+
Full school name
152+
city : str
153+
City name sometimes with abbreviations or fully written.
154+
"""
155+
name: str
156+
city: str
137157

138158
@dataclass
139159
class TaskData:
@@ -170,28 +190,22 @@ class TaskData:
170190
attachment: Optional[list[str]] = None
171191
attachment_url: Optional[ParseResult] = None
172192

173-
def requires_auth(f):
174-
@wraps(f)
175-
def decorated(*args, **kwargs):
176-
if not args[0].authenticated:
177-
args[0].logger.error("A2: Not authenticated.")
178-
return
179-
return f(*args, **kwargs)
180-
return decorated
181-
182193
def __init__(self,
183-
schoolid: str,
194+
school: str | School,
184195
username: str,
185196
password: str,
197+
save: bool = True,
186198
ad_header: httpx.Headers =
187199
httpx.Headers({ "user-agent":
188200
"LanisClient by kurwjan and contributors (https://github.com/kurwjan/LanisAPI/)" })
189201
) -> None:
190202

191-
self.schoolid = schoolid
203+
self.school = school
192204
self.username = username
193205
self.password = password
194206

207+
self.save = save
208+
195209
self.ad_header = ad_header
196210

197211
self.parser = httpx.Client(headers=ad_header)
@@ -207,12 +221,50 @@ def __init__(self,
207221
def __del__(self) -> None:
208222
self.parser.close()
209223

224+
def requires_auth(f):
225+
@wraps(f)
226+
def decorated(*args, **kwargs):
227+
if not args[0].authenticated:
228+
args[0].logger.error("A2: Not authenticated.")
229+
return
230+
return f(*args, **kwargs)
231+
return decorated
232+
210233
def close(self) -> None:
211234
"""Closes the client; you need to do this.
212235
"""
213236
self.parser.close()
214237
self.authenticated = False
215238

239+
def get_schools(self):
240+
"""
241+
Returns all schools with their id, name and city.
242+
243+
Returns
244+
-------
245+
list[dict[str, str]]
246+
JSON
247+
"""
248+
249+
if os.path.exists("schools.json"):
250+
with open("schools.json", "r") as file:
251+
return json.load(file)
252+
253+
url = "https://startcache.schulportal.hessen.de/exporteur.php"
254+
255+
response = self.parser.get(url, params=httpx.QueryParams({"a": "schoollist"})).json()
256+
257+
schools = []
258+
259+
for group in response:
260+
for school in group["Schulen"]:
261+
schools.append(school)
262+
263+
if self.save is True:
264+
with open("schools.json", "w") as file:
265+
json.dump(schools, file)
266+
267+
return schools
216268

217269
def authenticate(self) -> None:
218270
"""Logs into the school portal and sets the session id in the auth_cookies.
@@ -221,8 +273,21 @@ def authenticate(self) -> None:
221273
if self.authenticated:
222274
self.logger.warning("A1: Already authenticated.")
223275
return
276+
277+
school_id: int
278+
279+
if isinstance(self.school, str):
280+
school_id = self.school
281+
else:
282+
schools = self.get_schools()
283+
284+
try:
285+
school_id = next(school for school in schools if school["Name"] == self.school.name and school["Ort"] == self.school.city)["Id"]
286+
except StopIteration:
287+
self.logger.warning("E0: School doesn't exist check for right spelling.")
288+
return
224289

225-
response_session = get_session(self.schoolid, self.username,
290+
response_session = get_session(school_id, self.username,
226291
self.password,self.parser, self.ad_header)
227292
response_cookies = response_session["cookies"]
228293

@@ -234,7 +299,7 @@ def authenticate(self) -> None:
234299

235300
self.parser.cookies = get_authentication_data(auth_url, response_cookies,
236301
self.parser, self.ad_header,
237-
schoolid=self.schoolid)
302+
schoolid=school_id)
238303

239304
self.authenticated = True
240305

@@ -473,4 +538,4 @@ def get_tasks(self) -> list[TaskData]:
473538

474539
self.logger.info("D0: Successfully got tasks")
475540

476-
return task_list
541+
return task_list

test/schools.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)