pyedookit 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyedookit-0.3.0/.github/workflows/ci.yml +16 -0
- pyedookit-0.3.0/.github/workflows/publish.yml +34 -0
- pyedookit-0.3.0/.gitignore +12 -0
- pyedookit-0.3.0/LICENSE +21 -0
- pyedookit-0.3.0/PKG-INFO +111 -0
- pyedookit-0.3.0/README.md +97 -0
- pyedookit-0.3.0/pyproject.toml +47 -0
- pyedookit-0.3.0/scripts/capture_fixtures.py +75 -0
- pyedookit-0.3.0/src/pyedookit/__init__.py +33 -0
- pyedookit-0.3.0/src/pyedookit/auth.py +385 -0
- pyedookit-0.3.0/src/pyedookit/client.py +107 -0
- pyedookit-0.3.0/src/pyedookit/exceptions.py +22 -0
- pyedookit-0.3.0/src/pyedookit/models.py +66 -0
- pyedookit-0.3.0/src/pyedookit/parsers/__init__.py +0 -0
- pyedookit-0.3.0/src/pyedookit/parsers/dates.py +29 -0
- pyedookit-0.3.0/src/pyedookit/parsers/events.py +84 -0
- pyedookit-0.3.0/src/pyedookit/parsers/fingerprint.py +26 -0
- pyedookit-0.3.0/src/pyedookit/parsers/payments.py +82 -0
- pyedookit-0.3.0/src/pyedookit/parsers/timetable.py +114 -0
- pyedookit-0.3.0/src/pyedookit/session.py +50 -0
- pyedookit-0.3.0/tests/__init__.py +0 -0
- pyedookit-0.3.0/tests/fakes.py +118 -0
- pyedookit-0.3.0/tests/fixtures/payments_structure.html +70 -0
- pyedookit-0.3.0/tests/fixtures/timetable_real_structure.html +57 -0
- pyedookit-0.3.0/tests/fixtures/upcoming_events_structure.html +62 -0
- pyedookit-0.3.0/tests/test_auth.py +322 -0
- pyedookit-0.3.0/tests/test_client.py +117 -0
- pyedookit-0.3.0/tests/test_dates.py +36 -0
- pyedookit-0.3.0/tests/test_events_parser.py +86 -0
- pyedookit-0.3.0/tests/test_fingerprint.py +27 -0
- pyedookit-0.3.0/tests/test_payments_parser.py +45 -0
- pyedookit-0.3.0/tests/test_timetable_parser.py +89 -0
- pyedookit-0.3.0/uv.lock +1113 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: astral-sh/setup-uv@v6
|
|
13
|
+
- run: uv sync --group dev
|
|
14
|
+
- run: uv run pytest
|
|
15
|
+
- run: uv run ruff check .
|
|
16
|
+
- run: uv run mypy src
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Uses PyPI trusted publishing: register this repo + workflow + the "pypi"
|
|
4
|
+
# environment as a trusted publisher on pypi.org first, no API token needed.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v6
|
|
15
|
+
- run: uv sync --group dev
|
|
16
|
+
- run: uv run pytest
|
|
17
|
+
- run: uv build
|
|
18
|
+
- uses: actions/upload-artifact@v4
|
|
19
|
+
with:
|
|
20
|
+
name: dist
|
|
21
|
+
path: dist/
|
|
22
|
+
|
|
23
|
+
publish:
|
|
24
|
+
needs: build
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
environment: pypi
|
|
27
|
+
permissions:
|
|
28
|
+
id-token: write
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/download-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: dist
|
|
33
|
+
path: dist/
|
|
34
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
pyedookit-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tomas Liska
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyedookit-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyedookit
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Async client for the Edookit parent portal (edookit.net)
|
|
5
|
+
Project-URL: Homepage, https://github.com/TFoxik/pyedookit
|
|
6
|
+
Author: tomas.liska
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Requires-Dist: aiohttp>=3.9
|
|
11
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
12
|
+
Requires-Dist: lxml>=5.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# pyedookit
|
|
16
|
+
|
|
17
|
+
Unofficial async Python client for the Edookit parent portal
|
|
18
|
+
(`https://<school>.edookit.net`), used by Czech schools. Built for the
|
|
19
|
+
`ha-edookit` Home Assistant integration but usable standalone.
|
|
20
|
+
|
|
21
|
+
Edookit's official REST API exists but is only enabled per-school by an
|
|
22
|
+
administrator, so this library works the way a parent actually does: it logs
|
|
23
|
+
in through Plus4U (OIDC) and scrapes the portal's server-rendered pages.
|
|
24
|
+
There is no vendor support for this - selectors may break when Edookit
|
|
25
|
+
changes its markup.
|
|
26
|
+
|
|
27
|
+
## Status
|
|
28
|
+
|
|
29
|
+
v0.3.0 - timetable, upcoming school events and payments. Verified end-to-end against a real portal
|
|
30
|
+
(login, session-settling after the OIDC callback, and the timetable parser
|
|
31
|
+
all confirmed working on real data).
|
|
32
|
+
|
|
33
|
+
**Only `week_offset=0` (the current week) is confirmed working.** The
|
|
34
|
+
portal accepts `week_offset=1` ("next week") without error, but returns the
|
|
35
|
+
same week's data back rather than actually advancing - the real
|
|
36
|
+
week-navigation parameter hasn't been identified yet. Since a school's
|
|
37
|
+
weekly timetable is typically the same all year, current-week-only is
|
|
38
|
+
usually enough in practice. See `scripts/capture_fixtures.py` for how to
|
|
39
|
+
capture and inspect real portal HTML if you want to chase this further (a
|
|
40
|
+
browser HAR trace of clicking the forward-week arrow is what's needed).
|
|
41
|
+
|
|
42
|
+
Cancelled/substituted lessons are also unverified: no real capture has had
|
|
43
|
+
one yet, so `Lesson.is_changed`/`is_cancelled` are always `False` for now.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install pyedookit
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import asyncio
|
|
55
|
+
from pyedookit import login, EdookitClient
|
|
56
|
+
|
|
57
|
+
async def main():
|
|
58
|
+
session = await login("zs-example", "parent@example.com", "hunter2")
|
|
59
|
+
client = EdookitClient(session)
|
|
60
|
+
for lesson in await client.get_timetable(week_offset=0):
|
|
61
|
+
print(lesson.start, lesson.subject, lesson.room)
|
|
62
|
+
|
|
63
|
+
asyncio.run(main())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`client.get_events()` returns upcoming school events (`SchoolEvent`, all-day
|
|
67
|
+
events have `date` start/end with an inclusive end), and `client.get_payments()`
|
|
68
|
+
returns the family's payment totals and requests (`Payments`, amounts as
|
|
69
|
+
`Decimal`). Only paid requests have been seen on a real portal so far; a request
|
|
70
|
+
without the "accepted" marker is reported as unpaid.
|
|
71
|
+
|
|
72
|
+
`client.fetch_page(name)` returns the raw HTML of a page that has no parser yet
|
|
73
|
+
(`CAPTURE_PAGES`: assignments, exams, inbox, consents), and
|
|
74
|
+
`content_fingerprint(html)` tells you when such a page's content changed.
|
|
75
|
+
|
|
76
|
+
Persist `session.to_json()` / `Session.from_json()` between runs instead of
|
|
77
|
+
storing the password; use `pyedookit.refresh(session)` to renew the
|
|
78
|
+
short-lived edookit session from the long-lived Plus4U cookie, and fall back
|
|
79
|
+
to `login()` only when `refresh()` raises `AuthError`.
|
|
80
|
+
|
|
81
|
+
Lesson times are timezone-aware (`pyedookit.PORTAL_TZ`, Europe/Prague).
|
|
82
|
+
|
|
83
|
+
Errors all derive from `EdookitError`:
|
|
84
|
+
|
|
85
|
+
| Exception | Meaning | What to do |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `SessionExpired` | short-lived edookit session aged out | `refresh()` and retry |
|
|
88
|
+
| `AuthError` | bad credentials, or the Plus4U cookie expired | full `login()` |
|
|
89
|
+
| `EdookitConnectionError` | network error or 30 s timeout | retry later |
|
|
90
|
+
| `ParseError` | portal markup changed | parser needs updating |
|
|
91
|
+
|
|
92
|
+
`login()`, `refresh()` and `EdookitClient` all accept an optional
|
|
93
|
+
`http: aiohttp.ClientSession` to reuse. Cookies are handled by hand, so
|
|
94
|
+
create that session with `cookie_jar=aiohttp.DummyCookieJar()`; a session
|
|
95
|
+
with a real cookie jar would also store and resend the portal's cookies.
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv sync --group dev
|
|
101
|
+
uv run pytest
|
|
102
|
+
uv run ruff check .
|
|
103
|
+
uv run mypy src
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Releasing
|
|
107
|
+
|
|
108
|
+
Bump `version` in `pyproject.toml`, then push a `vX.Y.Z` tag. The
|
|
109
|
+
`Publish to PyPI` workflow tests, builds and uploads using PyPI trusted
|
|
110
|
+
publishing, which has to be configured once on pypi.org for this repository
|
|
111
|
+
and the `pypi` environment.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# pyedookit
|
|
2
|
+
|
|
3
|
+
Unofficial async Python client for the Edookit parent portal
|
|
4
|
+
(`https://<school>.edookit.net`), used by Czech schools. Built for the
|
|
5
|
+
`ha-edookit` Home Assistant integration but usable standalone.
|
|
6
|
+
|
|
7
|
+
Edookit's official REST API exists but is only enabled per-school by an
|
|
8
|
+
administrator, so this library works the way a parent actually does: it logs
|
|
9
|
+
in through Plus4U (OIDC) and scrapes the portal's server-rendered pages.
|
|
10
|
+
There is no vendor support for this - selectors may break when Edookit
|
|
11
|
+
changes its markup.
|
|
12
|
+
|
|
13
|
+
## Status
|
|
14
|
+
|
|
15
|
+
v0.3.0 - timetable, upcoming school events and payments. Verified end-to-end against a real portal
|
|
16
|
+
(login, session-settling after the OIDC callback, and the timetable parser
|
|
17
|
+
all confirmed working on real data).
|
|
18
|
+
|
|
19
|
+
**Only `week_offset=0` (the current week) is confirmed working.** The
|
|
20
|
+
portal accepts `week_offset=1` ("next week") without error, but returns the
|
|
21
|
+
same week's data back rather than actually advancing - the real
|
|
22
|
+
week-navigation parameter hasn't been identified yet. Since a school's
|
|
23
|
+
weekly timetable is typically the same all year, current-week-only is
|
|
24
|
+
usually enough in practice. See `scripts/capture_fixtures.py` for how to
|
|
25
|
+
capture and inspect real portal HTML if you want to chase this further (a
|
|
26
|
+
browser HAR trace of clicking the forward-week arrow is what's needed).
|
|
27
|
+
|
|
28
|
+
Cancelled/substituted lessons are also unverified: no real capture has had
|
|
29
|
+
one yet, so `Lesson.is_changed`/`is_cancelled` are always `False` for now.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install pyedookit
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import asyncio
|
|
41
|
+
from pyedookit import login, EdookitClient
|
|
42
|
+
|
|
43
|
+
async def main():
|
|
44
|
+
session = await login("zs-example", "parent@example.com", "hunter2")
|
|
45
|
+
client = EdookitClient(session)
|
|
46
|
+
for lesson in await client.get_timetable(week_offset=0):
|
|
47
|
+
print(lesson.start, lesson.subject, lesson.room)
|
|
48
|
+
|
|
49
|
+
asyncio.run(main())
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`client.get_events()` returns upcoming school events (`SchoolEvent`, all-day
|
|
53
|
+
events have `date` start/end with an inclusive end), and `client.get_payments()`
|
|
54
|
+
returns the family's payment totals and requests (`Payments`, amounts as
|
|
55
|
+
`Decimal`). Only paid requests have been seen on a real portal so far; a request
|
|
56
|
+
without the "accepted" marker is reported as unpaid.
|
|
57
|
+
|
|
58
|
+
`client.fetch_page(name)` returns the raw HTML of a page that has no parser yet
|
|
59
|
+
(`CAPTURE_PAGES`: assignments, exams, inbox, consents), and
|
|
60
|
+
`content_fingerprint(html)` tells you when such a page's content changed.
|
|
61
|
+
|
|
62
|
+
Persist `session.to_json()` / `Session.from_json()` between runs instead of
|
|
63
|
+
storing the password; use `pyedookit.refresh(session)` to renew the
|
|
64
|
+
short-lived edookit session from the long-lived Plus4U cookie, and fall back
|
|
65
|
+
to `login()` only when `refresh()` raises `AuthError`.
|
|
66
|
+
|
|
67
|
+
Lesson times are timezone-aware (`pyedookit.PORTAL_TZ`, Europe/Prague).
|
|
68
|
+
|
|
69
|
+
Errors all derive from `EdookitError`:
|
|
70
|
+
|
|
71
|
+
| Exception | Meaning | What to do |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `SessionExpired` | short-lived edookit session aged out | `refresh()` and retry |
|
|
74
|
+
| `AuthError` | bad credentials, or the Plus4U cookie expired | full `login()` |
|
|
75
|
+
| `EdookitConnectionError` | network error or 30 s timeout | retry later |
|
|
76
|
+
| `ParseError` | portal markup changed | parser needs updating |
|
|
77
|
+
|
|
78
|
+
`login()`, `refresh()` and `EdookitClient` all accept an optional
|
|
79
|
+
`http: aiohttp.ClientSession` to reuse. Cookies are handled by hand, so
|
|
80
|
+
create that session with `cookie_jar=aiohttp.DummyCookieJar()`; a session
|
|
81
|
+
with a real cookie jar would also store and resend the portal's cookies.
|
|
82
|
+
|
|
83
|
+
## Development
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uv sync --group dev
|
|
87
|
+
uv run pytest
|
|
88
|
+
uv run ruff check .
|
|
89
|
+
uv run mypy src
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Releasing
|
|
93
|
+
|
|
94
|
+
Bump `version` in `pyproject.toml`, then push a `vX.Y.Z` tag. The
|
|
95
|
+
`Publish to PyPI` workflow tests, builds and uploads using PyPI trusted
|
|
96
|
+
publishing, which has to be configured once on pypi.org for this repository
|
|
97
|
+
and the `pypi` environment.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyedookit"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Async client for the Edookit parent portal (edookit.net)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "tomas.liska" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"aiohttp>=3.9",
|
|
15
|
+
"beautifulsoup4>=4.12",
|
|
16
|
+
"lxml>=5.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/TFoxik/pyedookit"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/pyedookit"]
|
|
24
|
+
|
|
25
|
+
[tool.pytest.ini_options]
|
|
26
|
+
asyncio_mode = "auto"
|
|
27
|
+
testpaths = ["tests"]
|
|
28
|
+
|
|
29
|
+
[tool.ruff]
|
|
30
|
+
line-length = 100
|
|
31
|
+
target-version = "py312"
|
|
32
|
+
|
|
33
|
+
[tool.ruff.lint]
|
|
34
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
35
|
+
|
|
36
|
+
[tool.mypy]
|
|
37
|
+
strict = true
|
|
38
|
+
python_version = "3.12"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=8.0",
|
|
43
|
+
"pytest-asyncio>=0.24",
|
|
44
|
+
"multidict>=6.0",
|
|
45
|
+
"ruff>=0.6",
|
|
46
|
+
"mypy>=1.11",
|
|
47
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Log in to a real Edookit portal and save timetable HTML for inspection.
|
|
3
|
+
|
|
4
|
+
Run this yourself so credentials never enter the assistant's session:
|
|
5
|
+
|
|
6
|
+
! python scripts/capture_fixtures.py <school-subdomain>
|
|
7
|
+
|
|
8
|
+
It first discovers this school's Plus4U client_id (scraped from its own
|
|
9
|
+
/user/login page - it's per-school, not shared) without needing a password,
|
|
10
|
+
then prompts for username/password interactively (not via argv, so they
|
|
11
|
+
don't end up in shell history), logs in, and saves:
|
|
12
|
+
|
|
13
|
+
captures/timetable_current_week.html
|
|
14
|
+
captures/timetable_next_week.html
|
|
15
|
+
|
|
16
|
+
`captures/` is gitignored. These files come from an authenticated session
|
|
17
|
+
and can contain more than student/teacher names - a CSRF token, session
|
|
18
|
+
identifiers, or the parent's e-mail may be echoed into the page. Read the
|
|
19
|
+
printed warning before turning any of this into a committed test fixture.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
import getpass
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
|
|
30
|
+
|
|
31
|
+
from pyedookit import auth # noqa: E402
|
|
32
|
+
from pyedookit.client import EdookitClient # noqa: E402
|
|
33
|
+
|
|
34
|
+
CAPTURES_DIR = Path(__file__).resolve().parent.parent / "captures"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def main() -> None:
|
|
38
|
+
if len(sys.argv) != 2:
|
|
39
|
+
print("usage: capture_fixtures.py <school-subdomain>", file=sys.stderr)
|
|
40
|
+
raise SystemExit(2)
|
|
41
|
+
school = sys.argv[1]
|
|
42
|
+
|
|
43
|
+
client_id = await auth.probe_client_id(school)
|
|
44
|
+
print(f"Discovered Plus4U client_id for {school}: {client_id!r}")
|
|
45
|
+
|
|
46
|
+
username = input("Plus4U username/email: ")
|
|
47
|
+
password = getpass.getpass("Plus4U password: ")
|
|
48
|
+
|
|
49
|
+
print(f"Logging in to https://{school}.edookit.net ...")
|
|
50
|
+
session = await auth.login(school, username, password)
|
|
51
|
+
print("Login succeeded.")
|
|
52
|
+
|
|
53
|
+
client = EdookitClient(session)
|
|
54
|
+
CAPTURES_DIR.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
|
|
56
|
+
weeks = ((0, "timetable_current_week.html"), (1, "timetable_next_week.html"))
|
|
57
|
+
for week_offset, filename in weeks:
|
|
58
|
+
html = await client.fetch_timetable_html(week_offset)
|
|
59
|
+
(CAPTURES_DIR / filename).write_text(html)
|
|
60
|
+
print(f"Saved {filename}")
|
|
61
|
+
|
|
62
|
+
print()
|
|
63
|
+
print(f"Files were saved to {CAPTURES_DIR}/ (gitignored) rather than tests/fixtures/,")
|
|
64
|
+
print("because an authenticated page can contain more than names. Before turning any")
|
|
65
|
+
print("of this into a committed test fixture:")
|
|
66
|
+
print(" 1. Replace real student/teacher/parent names with placeholders.")
|
|
67
|
+
print(" 2. Scrub any CSRF token, session id, or e-mail address you find in the HTML.")
|
|
68
|
+
print(" 3. Confirm the day-column and lesson-entry markup matches the selectors in")
|
|
69
|
+
print(" src/pyedookit/parsers/timetable.py, and adjust them if the real markup")
|
|
70
|
+
print(" differs (view page source in a browser to compare class names).")
|
|
71
|
+
print(" 4. Only then copy the relevant snippet into tests/fixtures/ and `git add` it.")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from .auth import login, refresh
|
|
2
|
+
from .client import CAPTURE_PAGES, EdookitClient
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
AuthError,
|
|
5
|
+
EdookitConnectionError,
|
|
6
|
+
EdookitError,
|
|
7
|
+
ParseError,
|
|
8
|
+
SessionExpired,
|
|
9
|
+
)
|
|
10
|
+
from .models import PORTAL_TZ, Lesson, PaymentRequest, Payments, SchoolEvent, Student, Term
|
|
11
|
+
from .parsers.fingerprint import content_fingerprint
|
|
12
|
+
from .session import Session
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"CAPTURE_PAGES",
|
|
16
|
+
"PORTAL_TZ",
|
|
17
|
+
"AuthError",
|
|
18
|
+
"EdookitClient",
|
|
19
|
+
"EdookitConnectionError",
|
|
20
|
+
"EdookitError",
|
|
21
|
+
"Lesson",
|
|
22
|
+
"ParseError",
|
|
23
|
+
"PaymentRequest",
|
|
24
|
+
"Payments",
|
|
25
|
+
"SchoolEvent",
|
|
26
|
+
"Session",
|
|
27
|
+
"SessionExpired",
|
|
28
|
+
"Student",
|
|
29
|
+
"Term",
|
|
30
|
+
"content_fingerprint",
|
|
31
|
+
"login",
|
|
32
|
+
"refresh",
|
|
33
|
+
]
|