librus-python-api 1.7.0__tar.gz → 1.8.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.
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/API.md +52 -5
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/CHANGELOG.md +71 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/PKG-INFO +37 -6
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/README.md +36 -5
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/__init__.py +1 -1
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/config.py +92 -0
- librus_python_api-1.8.0/src/librus_python_api/diagnostics.py +444 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/formative_grades.py +6 -1
- librus_python_api-1.8.0/src/librus_python_api/gateway_grades.py +458 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/grade_parsers.py +28 -8
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/grade_records.py +22 -12
- librus_python_api-1.8.0/src/librus_python_api/inspect_page.py +114 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/markup.py +11 -1
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/models.py +2 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/service.py +94 -4
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/transport.py +20 -5
- librus_python_api-1.7.0/src/librus_python_api/diagnostics.py +0 -25
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/.gitignore +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/LICENSE +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/SECURITY.md +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/pyproject.toml +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/_notification_bootstrap.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/_notification_codec.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/_storage.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/_windows_filesystem.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/announcements.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/attachment_routes.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/attachments.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/attendance.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/attendance_frequency.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/budget.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/checkpoint.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/class_free_days.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/completed_lessons.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/detail_fields.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/exceptions.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/files.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/homework_range.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/lifecycle.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/message_content.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/messages.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/modern_body.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/modern_mailbox.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/modern_messages.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/notification_models.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/notification_persistence.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/notification_workflow.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/notifications.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/parsers.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/parsing.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/persistence.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/py.typed +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/recipients.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/scheduler.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/school_reads.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/school_year_archive.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/sending.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/src/librus_python_api/timetable.py +0 -0
- {librus_python_api-1.7.0 → librus_python_api-1.8.0}/uv.lock +0 -0
|
@@ -148,11 +148,38 @@ refunded. Exhaustion raises `LimitError`; deadlines raise `OperationTimeoutError
|
|
|
148
148
|
allowlisted modern directory and mailbox selections only. Arbitrary query keys,
|
|
149
149
|
routes and modern-to-legacy reference reuse are rejected before dispatch.
|
|
150
150
|
- `diagnostic_sink`: receives `DiagnosticEvent(operation, outcome,
|
|
151
|
-
elapsed_seconds, budget_requests_dispatched, budget_response_bytes)`.
|
|
152
|
-
`
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
elapsed_seconds, budget_requests_dispatched, budget_response_bytes, frames)`.
|
|
152
|
+
`frames` (1.8.0) holds the code locations of a `LibrusError` outcome, as below;
|
|
153
|
+
it is empty on success and for timeouts, cancellation and replaced
|
|
154
|
+
connection errors. `librus_python_api.diagnostics.logging_sink` emits allowlisted fields
|
|
155
|
+
through the standard `logging` logger named `librus_python_api`. The
|
|
156
|
+
application owns handlers and formatting; no logging dependency or global
|
|
157
|
+
configuration is added. Sink errors never affect results.
|
|
158
|
+
|
|
159
|
+
### Failure diagnostics (1.8.0)
|
|
160
|
+
|
|
161
|
+
Errors carry only their kind, so these helpers read where a failure happened
|
|
162
|
+
from the exception's own traceback. Neither raises.
|
|
163
|
+
|
|
164
|
+
- `diagnostics.failure_frames(error, limit=6, packages=("librus_python_api",))`
|
|
165
|
+
returns code locations such as `("grade_records:_numeric:100",
|
|
166
|
+
"markup:tooltip_fields:113")`, the original failure site last. It follows an
|
|
167
|
+
explicit cause or a context suppressed with `from None`, and skips helpers
|
|
168
|
+
that only raise. The strings are opaque and change between releases; report
|
|
169
|
+
them with the library version.
|
|
170
|
+
- `diagnostics.describe_failure(error)` returns the page structure around the
|
|
171
|
+
failure, or None: the HTML elements held by the innermost library frame that
|
|
172
|
+
holds any, with their path from the grade table, tags, safe class tokens,
|
|
173
|
+
known tooltip labels (`config.TOOLTIP_LABELS`), known detail-link families and
|
|
174
|
+
lengths. Text and attribute values are masked unless they equal a parser
|
|
175
|
+
constant, so a failing tooltip reads like `[[Data], [Waga], NO-COLON(len=17)]`.
|
|
176
|
+
Output is bounded. It is meant for a human to review before sharing, not for
|
|
177
|
+
automatic upload.
|
|
178
|
+
- `python -m librus_python_api.inspect_page {grades,final-grades} FILE` parses a
|
|
179
|
+
saved page offline (a whole page or a copied table) and prints counts, or the
|
|
180
|
+
failure kind, frames and structure. It makes no request and applies the
|
|
181
|
+
library's parse size limit. Exit status: 0 parsed, 1 failed, 2 unreadable
|
|
182
|
+
file.
|
|
156
183
|
|
|
157
184
|
All upstream routes and forms are fixed in `config.py` and documented in
|
|
158
185
|
[contracts/upstream.openapi.yaml](contracts/upstream.openapi.yaml). There is no
|
|
@@ -249,6 +276,26 @@ such as `(0-100)`, not a grade symbol. The table's "Wyn." period results are
|
|
|
249
276
|
into `averages`), and a point table with titled summary columns adds its
|
|
250
277
|
subjects to `final_grades()`. See [point grades](contracts/grades.md#point-grades-170).
|
|
251
278
|
|
|
279
|
+
Gateway descriptive grades (1.8.0) belong to schools on the new
|
|
280
|
+
descriptive grading schema, whose grades page shows a subject table with empty
|
|
281
|
+
semester cells that the page script fills from the gateway. In the `ALL` view
|
|
282
|
+
only, `grades()` then reads those grades from the gateway with the login's
|
|
283
|
+
session and the call's budget: three more requests, five when there are
|
|
284
|
+
grades. They are `DescriptiveGrade`s in `descriptive` (kind `CURRENT`, no
|
|
285
|
+
`href`), with the page's subject name, the scale value or else the content as
|
|
286
|
+
`raw`, the civil date, semester 1 or 2, the teacher's name when listed, the
|
|
287
|
+
comment and metadata pairs for the present fields only (`Obszar`, `Treść`,
|
|
288
|
+
`Dodał`, `Komentarz`, `Skala`). `grades_window()` filters them like other
|
|
289
|
+
dated records. `WEEK` and `LAST_LOGIN` do not include them, because the page
|
|
290
|
+
narrows those views by a field whose meaning is not established; notifications
|
|
291
|
+
therefore do not report them. A gateway error, an unknown subject, a grade with
|
|
292
|
+
no value, an unexpected shape or a pagination member that shows more items
|
|
293
|
+
fails the whole read instead of returning it without these grades; a refused
|
|
294
|
+
gateway route (403) is `UNSUPPORTED_CAPABILITY`, so it does not put the other
|
|
295
|
+
grade views into the access-denied cooldown. The routes and field names are
|
|
296
|
+
source-informed; a first authorized live read parsed populated records on one
|
|
297
|
+
school's login. See [gateway descriptive grades](contracts/grades.md#gateway-descriptive-grades-180-issue-73).
|
|
298
|
+
|
|
252
299
|
## Attendance
|
|
253
300
|
|
|
254
301
|
- `attendance(view=AttendanceView.ALL)` returns `Attendance` with
|
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.8.0 (2026-10-28) - Failure diagnostics and gateway descriptive grades
|
|
4
|
+
|
|
5
|
+
Errors still carry only their kind; new helpers say where a failure happened
|
|
6
|
+
without page data. Schools on the new descriptive grading schema get their
|
|
7
|
+
gateway grades.
|
|
8
|
+
|
|
9
|
+
### Failure diagnostics
|
|
10
|
+
|
|
11
|
+
- `diagnostics.failure_frames(error)` returns the failure's code locations
|
|
12
|
+
(`module:function:line`, the original site last) from its traceback, through
|
|
13
|
+
the parser thread, re-raises and a context suppressed with `from None`.
|
|
14
|
+
- `diagnostics.describe_failure(error)` returns the page structure around the
|
|
15
|
+
failure: element path, tags, safe class tokens, known tooltip labels and
|
|
16
|
+
lengths, with every text and attribute value masked unless it equals a parser
|
|
17
|
+
constant. It names, for example, the unlabelled tooltip line that broke a
|
|
18
|
+
read.
|
|
19
|
+
- `python -m librus_python_api.inspect_page {grades,final-grades} FILE` parses a
|
|
20
|
+
saved page or copied table offline and prints counts or the failure report.
|
|
21
|
+
- `DiagnosticEvent.frames` carries the code locations to diagnostic sinks, and
|
|
22
|
+
`logging_sink` logs them.
|
|
23
|
+
- `config.TOOLTIP_LABELS` lists the tooltip labels diagnostics may show by name.
|
|
24
|
+
|
|
25
|
+
### Gateway descriptive grades
|
|
26
|
+
|
|
27
|
+
Reads the grades of schools on the new descriptive grading schema, whose
|
|
28
|
+
grades page renders a subject table with empty semester cells and loads the
|
|
29
|
+
grades from gateway JSON routes. Routes and field names come from public client
|
|
30
|
+
code and the official page script. A first authorized live read on 2026-10-28
|
|
31
|
+
parsed four populated records on one school's login and showed the page
|
|
32
|
+
layout below (issue #73).
|
|
33
|
+
|
|
34
|
+
- Grades: in the `ALL` view, a page with the new-schema table adds its gateway
|
|
35
|
+
grades to `GradeRecords.descriptive` as `DescriptiveGrade` records, which
|
|
36
|
+
grade windows filter by date. The read makes three more requests, or five
|
|
37
|
+
when there are grades, under the same session and budget. Public types and
|
|
38
|
+
signatures are unchanged and `DescriptiveGrade` has no new field.
|
|
39
|
+
- Grades: `WEEK` and `LAST_LOGIN` make no gateway request and do not include
|
|
40
|
+
these grades, so notifications do not report them yet.
|
|
41
|
+
- Grades: a gateway error, unknown subject, grade without a value, malformed
|
|
42
|
+
or oversized response, unknown table layout or a pagination member showing
|
|
43
|
+
more items fails the whole all-view read on such a page, instead of
|
|
44
|
+
returning it without these grades. Pages without the table are unchanged.
|
|
45
|
+
A refused gateway route (403) is `UnsupportedCapabilityError`, so it does
|
|
46
|
+
not put the week and last-login views into the access-denied cooldown.
|
|
47
|
+
- Upstream contract: five source-informed gateway operations; the two that
|
|
48
|
+
take an account identifier accept `[A-Za-z0-9_-]{1,128}`, every other
|
|
49
|
+
`{id}` route stays numeric.
|
|
50
|
+
- Live check: the weekly grade checks and `scripts/live_capture.py` declare
|
|
51
|
+
the five gateway routes.
|
|
52
|
+
- The page may hold an unclosed, row-less table of the same class before the
|
|
53
|
+
real one, and the real table has a period header row, hidden details rows
|
|
54
|
+
and a footer; only a table with subject rows of its own is read.
|
|
55
|
+
|
|
56
|
+
## 1.7.1 (2026-10-27) - Observed point-grade page and multi-line comments
|
|
57
|
+
|
|
58
|
+
Fixes for layouts 1.7.0 rejected with `ParseError`, established from an
|
|
59
|
+
affected school's real point-grade page and a live grade read. Public types
|
|
60
|
+
and signatures are unchanged.
|
|
61
|
+
|
|
62
|
+
- Grades: a teacher's multi-line `Komentarz` continues on unlabelled tooltip
|
|
63
|
+
lines and is kept with its line breaks. Every grade read of an account with
|
|
64
|
+
such a comment failed in all releases up to 1.7.0; any other unlabelled
|
|
65
|
+
tooltip line still fails.
|
|
66
|
+
- Point grades: a formative entry among the point entries is accepted as the
|
|
67
|
+
formative mirror it is; the empty footer row wider than the table is
|
|
68
|
+
skipped; titles that break their own label (`Ocena śródroczna <br> z
|
|
69
|
+
pierwszego okresu`) name the period columns, so point midterm grades are
|
|
70
|
+
read. These rules apply to point tables only.
|
|
71
|
+
- Formative assessments: assessment text may contain line breaks. 1.7.0 and
|
|
72
|
+
earlier failed such a table with `UnsupportedCapabilityError`.
|
|
73
|
+
|
|
3
74
|
## 1.7.0 (2026-10-27) - Point grades
|
|
4
75
|
|
|
5
76
|
Reads the "Oceny punktowe" table that point-grade schools show on the grades
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: librus-python-api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: Independent bounded async Librus Synergia client
|
|
5
5
|
Project-URL: Repository, https://github.com/krzysztofbury/librus-python-api
|
|
6
6
|
Project-URL: Issues, https://github.com/krzysztofbury/librus-python-api/issues
|
|
@@ -42,8 +42,11 @@ private ACLs. Windows installations include `pywin32` and `tzdata` automatically
|
|
|
42
42
|
Network paths, reparse-point paths and unsafe storage permissions fail closed.
|
|
43
43
|
See [platform requirements](contracts/persistence.md#windows-disk-boundary-25).
|
|
44
44
|
|
|
45
|
-
**Status:** `1.
|
|
46
|
-
|
|
45
|
+
**Status:** `1.8.0` reports where a failure happened (code locations and a
|
|
46
|
+
redacted page structure, see [failure diagnostics](API.md#failure-diagnostics-180)),
|
|
47
|
+
reads gateway descriptive grades for schools on the new descriptive schema
|
|
48
|
+
(source-informed, first populated read live on one school) and keeps the 1.7.1 point
|
|
49
|
+
grades and multi-line grade comments. It keeps the 1.6.4 linear-time message body
|
|
47
50
|
rendering and the 1.6.2 completed-lesson and grade layouts, including behaviour
|
|
48
51
|
points, which are validated and not reported as grades.
|
|
49
52
|
Class free days, typed capability outcomes and the live-check freshness watchdog
|
|
@@ -57,7 +60,7 @@ See [limitations](#supported-features-and-limitations) below.
|
|
|
57
60
|
Install the exact release from PyPI after its gated publication completes:
|
|
58
61
|
|
|
59
62
|
```sh
|
|
60
|
-
python -m pip install librus-python-api==1.
|
|
63
|
+
python -m pip install librus-python-api==1.8.0
|
|
61
64
|
```
|
|
62
65
|
|
|
63
66
|
Pin the exact version qualified by your application. Before publication, use a
|
|
@@ -73,10 +76,12 @@ python -m pip install .
|
|
|
73
76
|
To install a locally built wheel instead:
|
|
74
77
|
|
|
75
78
|
```sh
|
|
76
|
-
python -m pip install ./dist/librus_python_api-1.
|
|
79
|
+
python -m pip install ./dist/librus_python_api-1.8.0-py3-none-any.whl
|
|
77
80
|
```
|
|
78
81
|
|
|
79
|
-
No
|
|
82
|
+
No console script or background process is installed: import the library in your
|
|
83
|
+
own program. `python -m librus_python_api.inspect_page` only parses a saved page
|
|
84
|
+
offline for a bug report.
|
|
80
85
|
|
|
81
86
|
## First request
|
|
82
87
|
|
|
@@ -254,6 +259,32 @@ unqualified; backend acceptance is not proof of delivery. Tests cover supported
|
|
|
254
259
|
contracts, not every school or role. Detailed coverage is in the
|
|
255
260
|
[verification log][verification]; open work is tracked in [issues][issues].
|
|
256
261
|
|
|
262
|
+
## Diagnosing failures
|
|
263
|
+
|
|
264
|
+
Errors carry only their kind. To see where a failure happened without any page
|
|
265
|
+
data, use the helpers in `librus_python_api.diagnostics` (see
|
|
266
|
+
[API.md](API.md#failure-diagnostics-180)):
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
from librus_python_api.diagnostics import describe_failure, failure_frames
|
|
270
|
+
from librus_python_api.exceptions import LibrusError
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
await client.grades()
|
|
274
|
+
except LibrusError as error:
|
|
275
|
+
print(
|
|
276
|
+
error.kind, failure_frames(error)
|
|
277
|
+
) # ('grade_records:_numeric:100', 'markup:tooltip_fields:113')
|
|
278
|
+
print(describe_failure(error)) # masked HTML structure around the failure
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
To check a saved page offline (a page source saved with Ctrl+U, or a copied
|
|
282
|
+
table), run `python -m librus_python_api.inspect_page grades page.html` (or
|
|
283
|
+
`final-grades`). It prints counts, or the failure's code locations and masked
|
|
284
|
+
structure, and makes no request. Pass a `diagnostic_sink` to `LibrusService`
|
|
285
|
+
to receive one `DiagnosticEvent` per operation, with `frames` on failure.
|
|
286
|
+
None of these print page text, values, logins or exception messages.
|
|
287
|
+
|
|
257
288
|
## Development and support
|
|
258
289
|
|
|
259
290
|
Use a repository checkout for tests and development tools; they are deliberately
|
|
@@ -16,8 +16,11 @@ private ACLs. Windows installations include `pywin32` and `tzdata` automatically
|
|
|
16
16
|
Network paths, reparse-point paths and unsafe storage permissions fail closed.
|
|
17
17
|
See [platform requirements](contracts/persistence.md#windows-disk-boundary-25).
|
|
18
18
|
|
|
19
|
-
**Status:** `1.
|
|
20
|
-
|
|
19
|
+
**Status:** `1.8.0` reports where a failure happened (code locations and a
|
|
20
|
+
redacted page structure, see [failure diagnostics](API.md#failure-diagnostics-180)),
|
|
21
|
+
reads gateway descriptive grades for schools on the new descriptive schema
|
|
22
|
+
(source-informed, first populated read live on one school) and keeps the 1.7.1 point
|
|
23
|
+
grades and multi-line grade comments. It keeps the 1.6.4 linear-time message body
|
|
21
24
|
rendering and the 1.6.2 completed-lesson and grade layouts, including behaviour
|
|
22
25
|
points, which are validated and not reported as grades.
|
|
23
26
|
Class free days, typed capability outcomes and the live-check freshness watchdog
|
|
@@ -31,7 +34,7 @@ See [limitations](#supported-features-and-limitations) below.
|
|
|
31
34
|
Install the exact release from PyPI after its gated publication completes:
|
|
32
35
|
|
|
33
36
|
```sh
|
|
34
|
-
python -m pip install librus-python-api==1.
|
|
37
|
+
python -m pip install librus-python-api==1.8.0
|
|
35
38
|
```
|
|
36
39
|
|
|
37
40
|
Pin the exact version qualified by your application. Before publication, use a
|
|
@@ -47,10 +50,12 @@ python -m pip install .
|
|
|
47
50
|
To install a locally built wheel instead:
|
|
48
51
|
|
|
49
52
|
```sh
|
|
50
|
-
python -m pip install ./dist/librus_python_api-1.
|
|
53
|
+
python -m pip install ./dist/librus_python_api-1.8.0-py3-none-any.whl
|
|
51
54
|
```
|
|
52
55
|
|
|
53
|
-
No
|
|
56
|
+
No console script or background process is installed: import the library in your
|
|
57
|
+
own program. `python -m librus_python_api.inspect_page` only parses a saved page
|
|
58
|
+
offline for a bug report.
|
|
54
59
|
|
|
55
60
|
## First request
|
|
56
61
|
|
|
@@ -228,6 +233,32 @@ unqualified; backend acceptance is not proof of delivery. Tests cover supported
|
|
|
228
233
|
contracts, not every school or role. Detailed coverage is in the
|
|
229
234
|
[verification log][verification]; open work is tracked in [issues][issues].
|
|
230
235
|
|
|
236
|
+
## Diagnosing failures
|
|
237
|
+
|
|
238
|
+
Errors carry only their kind. To see where a failure happened without any page
|
|
239
|
+
data, use the helpers in `librus_python_api.diagnostics` (see
|
|
240
|
+
[API.md](API.md#failure-diagnostics-180)):
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from librus_python_api.diagnostics import describe_failure, failure_frames
|
|
244
|
+
from librus_python_api.exceptions import LibrusError
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
await client.grades()
|
|
248
|
+
except LibrusError as error:
|
|
249
|
+
print(
|
|
250
|
+
error.kind, failure_frames(error)
|
|
251
|
+
) # ('grade_records:_numeric:100', 'markup:tooltip_fields:113')
|
|
252
|
+
print(describe_failure(error)) # masked HTML structure around the failure
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
To check a saved page offline (a page source saved with Ctrl+U, or a copied
|
|
256
|
+
table), run `python -m librus_python_api.inspect_page grades page.html` (or
|
|
257
|
+
`final-grades`). It prints counts, or the failure's code locations and masked
|
|
258
|
+
structure, and makes no request. Pass a `diagnostic_sink` to `LibrusService`
|
|
259
|
+
to receive one `DiagnosticEvent` per operation, with `frames` on failure.
|
|
260
|
+
None of these print page text, values, logins or exception messages.
|
|
261
|
+
|
|
231
262
|
## Development and support
|
|
232
263
|
|
|
233
264
|
Use a repository checkout for tests and development tools; they are deliberately
|
|
@@ -138,7 +138,7 @@ from librus_python_api.models import (
|
|
|
138
138
|
from librus_python_api.sending import SendAttempt
|
|
139
139
|
from librus_python_api.service import AccountClient, LibrusService
|
|
140
140
|
|
|
141
|
-
__version__ = "1.
|
|
141
|
+
__version__ = "1.8.0"
|
|
142
142
|
|
|
143
143
|
__all__ = [
|
|
144
144
|
"ArchiveAbsences",
|
|
@@ -433,6 +433,51 @@ ENDPOINTS: Mapping[str, Endpoint] = MappingProxyType(
|
|
|
433
433
|
False,
|
|
434
434
|
Evidence.INDEPENDENTLY_OBSERVED,
|
|
435
435
|
),
|
|
436
|
+
# Gateway reads behind the grades page's new-schema descriptive
|
|
437
|
+
# table. Routes and field names come from public client code and the
|
|
438
|
+
# official page script, not from an observed populated response.
|
|
439
|
+
Endpoint(
|
|
440
|
+
"descriptive_token_info",
|
|
441
|
+
"GET",
|
|
442
|
+
"/gateway/api/2.0/Auth/TokenInfo",
|
|
443
|
+
SideEffect.NONE,
|
|
444
|
+
True,
|
|
445
|
+
Evidence.SOURCE_INFORMED,
|
|
446
|
+
),
|
|
447
|
+
Endpoint(
|
|
448
|
+
"descriptive_user_info",
|
|
449
|
+
"GET",
|
|
450
|
+
"/gateway/api/2.0/Auth/UserInfo/{id}",
|
|
451
|
+
SideEffect.NONE,
|
|
452
|
+
True,
|
|
453
|
+
Evidence.SOURCE_INFORMED,
|
|
454
|
+
),
|
|
455
|
+
# A read sent as POST with the fixed JSON body `{}`; it is never
|
|
456
|
+
# replayed, although it changes nothing that is known.
|
|
457
|
+
Endpoint(
|
|
458
|
+
"descriptive_partial_grades",
|
|
459
|
+
"POST",
|
|
460
|
+
"/gateway/api/2.0/Auth/DescriptiveGradingSystem/PartialGrades/Student/{id}",
|
|
461
|
+
SideEffect.NONE,
|
|
462
|
+
False,
|
|
463
|
+
Evidence.SOURCE_INFORMED,
|
|
464
|
+
),
|
|
465
|
+
Endpoint(
|
|
466
|
+
"descriptive_subjects",
|
|
467
|
+
"GET",
|
|
468
|
+
"/gateway/api/3.0/Auth/Subjects",
|
|
469
|
+
SideEffect.NONE,
|
|
470
|
+
True,
|
|
471
|
+
Evidence.SOURCE_INFORMED,
|
|
472
|
+
),
|
|
473
|
+
Endpoint(
|
|
474
|
+
"descriptive_users",
|
|
475
|
+
"GET",
|
|
476
|
+
"/gateway/api/2.0/Users",
|
|
477
|
+
SideEffect.NONE,
|
|
478
|
+
True,
|
|
479
|
+
Evidence.SOURCE_INFORMED,
|
|
480
|
+
),
|
|
436
481
|
Endpoint(
|
|
437
482
|
"attendance",
|
|
438
483
|
"POST",
|
|
@@ -659,6 +704,38 @@ GRADE_MAX_SUBJECTS = 128
|
|
|
659
704
|
GRADE_MAX_VALUE_LENGTH = 1024
|
|
660
705
|
GRADE_MERGED_SUBJECTS = frozenset({"Zachowanie"})
|
|
661
706
|
GRADE_INLINE_DETAIL_LABEL = "Ocena"
|
|
707
|
+
# Tooltip fields whose free text may continue on unlabelled lines. A teacher's
|
|
708
|
+
# multi-line grade comment was observed on 2026-10-27.
|
|
709
|
+
TOOLTIP_MULTILINE_LABELS = frozenset({"Komentarz"})
|
|
710
|
+
# Tooltip labels observed on grade, attendance and point pages. Diagnostics
|
|
711
|
+
# show only these by name; any other label is masked as teacher-entered text.
|
|
712
|
+
TOOLTIP_LABELS = frozenset(
|
|
713
|
+
{
|
|
714
|
+
"Czy wycieczka",
|
|
715
|
+
"Data",
|
|
716
|
+
"Data realizacji",
|
|
717
|
+
"Data wystawienia",
|
|
718
|
+
"Data zapowiedzi",
|
|
719
|
+
"Dodał",
|
|
720
|
+
"Godzina lekcyjna",
|
|
721
|
+
"Kategoria",
|
|
722
|
+
"Komentarz",
|
|
723
|
+
"Lekcja",
|
|
724
|
+
"Licz do puli",
|
|
725
|
+
"Licz do wyniku",
|
|
726
|
+
"Licz do średniej",
|
|
727
|
+
"Maksymalna ilość punktów",
|
|
728
|
+
"Nauczyciel",
|
|
729
|
+
"Obowiązek wyk. zadania",
|
|
730
|
+
"Ocena",
|
|
731
|
+
"Rodzaj",
|
|
732
|
+
"Suma punktów",
|
|
733
|
+
"Temat zajęć",
|
|
734
|
+
"Waga",
|
|
735
|
+
"Zakres do",
|
|
736
|
+
"Zakres od",
|
|
737
|
+
}
|
|
738
|
+
)
|
|
662
739
|
# A point-grade ("Oceny punktowe") table marks each subject row's expander image
|
|
663
740
|
# with this ID; ordinary grade rows use `przedmioty_<id>_node`.
|
|
664
741
|
GRADE_POINT_ROW_NODE = re.compile(r"przedmioty_OP_[0-9A-Za-z_]{1,64}_node")
|
|
@@ -690,6 +767,21 @@ GRADE_PERIOD_HEADERS = MappingProxyType(
|
|
|
690
767
|
"Ocena roczna": 0,
|
|
691
768
|
}
|
|
692
769
|
)
|
|
770
|
+
# New-schema descriptive grading: the page renders this table with empty
|
|
771
|
+
# semester cells and its script loads the grades from the gateway routes above.
|
|
772
|
+
DESCRIPTIVE_TABLE_CLASS = "newSchemaGradesByStudentTable"
|
|
773
|
+
DESCRIPTIVE_ROW_CLASS = "studentRow"
|
|
774
|
+
# Gateway account and student identifiers are opaque strings, unlike every
|
|
775
|
+
# other `{id}` route, which takes a numeric ID.
|
|
776
|
+
DESCRIPTIVE_IDENTIFIER = re.compile(r"[A-Za-z0-9_-]{1,128}")
|
|
777
|
+
DESCRIPTIVE_IDENTIFIER_OPERATIONS = frozenset(
|
|
778
|
+
{"descriptive_user_info", "descriptive_partial_grades"}
|
|
779
|
+
)
|
|
780
|
+
# The PartialGrades read is a POST whose only body is this fixed JSON object.
|
|
781
|
+
DESCRIPTIVE_PARTIAL_GRADES_BODY = b"{}"
|
|
782
|
+
DESCRIPTIVE_MAX_SUBJECTS = 1024
|
|
783
|
+
DESCRIPTIVE_MAX_USERS = 8192
|
|
784
|
+
DESCRIPTIVE_MAX_NAME_LENGTH = 1024
|
|
693
785
|
GRADE_PREDICTED_ANNUAL_HEADER = "Przewidywana ocena roczna"
|
|
694
786
|
GRADE_PREDICTED_PERIOD_HEADERS = MappingProxyType(
|
|
695
787
|
{
|