librus-python-api 1.8.1__tar.gz → 1.8.2__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.8.1 → librus_python_api-1.8.2}/API.md +3 -1
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/CHANGELOG.md +9 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/PKG-INFO +5 -4
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/README.md +4 -3
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/__init__.py +3 -2
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/attachments.py +5 -4
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/config.py +6 -3
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/exceptions.py +3 -1
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/gateway_grades.py +3 -2
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/service.py +30 -14
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/transport.py +16 -10
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/.gitignore +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/LICENSE +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/SECURITY.md +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/pyproject.toml +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/_notification_bootstrap.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/_notification_codec.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/_storage.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/_windows_filesystem.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/announcements.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/attachment_routes.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/attendance.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/attendance_frequency.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/budget.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/checkpoint.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/class_free_days.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/completed_lessons.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/detail_fields.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/diagnostics.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/files.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/formative_grades.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/grade_parsers.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/grade_records.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/homework_range.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/inspect_page.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/lifecycle.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/markup.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/message_content.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/messages.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/models.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/modern_body.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/modern_mailbox.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/modern_messages.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/notification_models.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/notification_persistence.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/notification_workflow.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/notifications.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/parsers.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/parsing.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/persistence.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/py.typed +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/recipients.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/scheduler.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/school_reads.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/school_year_archive.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/sending.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/timetable.py +0 -0
- {librus_python_api-1.8.1 → librus_python_api-1.8.2}/uv.lock +0 -0
|
@@ -470,7 +470,9 @@ The initial handoff already includes that GET; subsequent sends cost one GET plu
|
|
|
470
470
|
one POST. Expiry before dispatch raises `SessionExpiredError` and leaves
|
|
471
471
|
NOT_DISPATCHED. An exact launch-to-native-login redirect is expiry, not denial.
|
|
472
472
|
Messages-origin failures clear only modern binding/cookies/cache; valid legacy
|
|
473
|
-
sessions stay usable. A
|
|
473
|
+
sessions stay usable. A cancelled mailbox read, or one stopped by a reached limit,
|
|
474
|
+
keeps them, so the next call needs no new launch; a limit that emptied the cookie
|
|
475
|
+
jar does not, and neither does a transport timeout. A later explicit modern call can rebind, but no modern
|
|
474
476
|
read/send is automatically retried. A race after preflight can still yield UNKNOWN.
|
|
475
477
|
|
|
476
478
|
- `modern_identity(*, budget=None, max_age_seconds=0)` returns `ModernIdentity`:
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.8.2 (2026-11-01) - Modern session kept across cancels, honest User-Agent
|
|
4
|
+
|
|
5
|
+
- A cancelled modern mailbox read or a reached limit no longer drops the modern
|
|
6
|
+
session, so the next call needs no new launch. Other failures still drop it,
|
|
7
|
+
an unrecognized body and a cookie limit, which empties the jar, included.
|
|
8
|
+
- The `User-Agent` carries the package version; it said 0.4 since that release.
|
|
9
|
+
- `config.ATTENDANCE_RESULT_CACHE_SIZE` is renamed `ACCOUNT_RESULT_CACHE_SIZE`:
|
|
10
|
+
it caps every cached read of an account.
|
|
11
|
+
|
|
3
12
|
## 1.8.1 (2026-10-29) - Session recovery, lucky number, consent error and diagnostics
|
|
4
13
|
|
|
5
14
|
Signatures are unchanged; one error kind is added.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: librus-python-api
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.2
|
|
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,7 +42,8 @@ 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.8.
|
|
45
|
+
**Status:** `1.8.2` keeps the modern mailbox session across a cancelled read
|
|
46
|
+
or a reached limit and sends its own version in the `User-Agent`. `1.8.1` recovers a session Synergia no longer knows (it answers
|
|
46
47
|
with a "Brak dostępu" page, not a login redirect), reads the lucky number
|
|
47
48
|
from the page header and summarizes a page that fails as a whole in
|
|
48
49
|
diagnostics. `1.8.0` reports where a failure
|
|
@@ -63,7 +64,7 @@ See [limitations](#supported-features-and-limitations) below.
|
|
|
63
64
|
Install the exact release from PyPI after its gated publication completes:
|
|
64
65
|
|
|
65
66
|
```sh
|
|
66
|
-
python -m pip install librus-python-api==1.8.
|
|
67
|
+
python -m pip install librus-python-api==1.8.2
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
Pin the exact version qualified by your application. Before publication, use a
|
|
@@ -79,7 +80,7 @@ python -m pip install .
|
|
|
79
80
|
To install a locally built wheel instead:
|
|
80
81
|
|
|
81
82
|
```sh
|
|
82
|
-
python -m pip install ./dist/librus_python_api-1.8.
|
|
83
|
+
python -m pip install ./dist/librus_python_api-1.8.2-py3-none-any.whl
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
No console script or background process is installed: import the library in your
|
|
@@ -16,7 +16,8 @@ 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.8.
|
|
19
|
+
**Status:** `1.8.2` keeps the modern mailbox session across a cancelled read
|
|
20
|
+
or a reached limit and sends its own version in the `User-Agent`. `1.8.1` recovers a session Synergia no longer knows (it answers
|
|
20
21
|
with a "Brak dostępu" page, not a login redirect), reads the lucky number
|
|
21
22
|
from the page header and summarizes a page that fails as a whole in
|
|
22
23
|
diagnostics. `1.8.0` reports where a failure
|
|
@@ -37,7 +38,7 @@ See [limitations](#supported-features-and-limitations) below.
|
|
|
37
38
|
Install the exact release from PyPI after its gated publication completes:
|
|
38
39
|
|
|
39
40
|
```sh
|
|
40
|
-
python -m pip install librus-python-api==1.8.
|
|
41
|
+
python -m pip install librus-python-api==1.8.2
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
Pin the exact version qualified by your application. Before publication, use a
|
|
@@ -53,7 +54,7 @@ python -m pip install .
|
|
|
53
54
|
To install a locally built wheel instead:
|
|
54
55
|
|
|
55
56
|
```sh
|
|
56
|
-
python -m pip install ./dist/librus_python_api-1.8.
|
|
57
|
+
python -m pip install ./dist/librus_python_api-1.8.2-py3-none-any.whl
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
No console script or background process is installed: import the library in your
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""Independent bounded async client. General live compatibility is unqualified."""
|
|
2
2
|
|
|
3
|
+
# Before the imports: config reads it for the User-Agent.
|
|
4
|
+
__version__ = "1.8.2"
|
|
5
|
+
|
|
3
6
|
from librus_python_api.attachments import AttachmentStream, ModernAttachmentStream
|
|
4
7
|
from librus_python_api.budget import RequestBudget
|
|
5
8
|
from librus_python_api.config import (
|
|
@@ -138,8 +141,6 @@ from librus_python_api.models import (
|
|
|
138
141
|
from librus_python_api.sending import SendAttempt
|
|
139
142
|
from librus_python_api.service import AccountClient, LibrusService
|
|
140
143
|
|
|
141
|
-
__version__ = "1.8.1"
|
|
142
|
-
|
|
143
144
|
__all__ = [
|
|
144
145
|
"ArchiveAbsences",
|
|
145
146
|
"ArchiveAchievement",
|
|
@@ -15,7 +15,10 @@ from librus_python_api.attachment_routes import (
|
|
|
15
15
|
)
|
|
16
16
|
from librus_python_api.budget import RequestBudget
|
|
17
17
|
from librus_python_api.config import ATTACHMENT_MAX_BYTES
|
|
18
|
-
from librus_python_api.exceptions import
|
|
18
|
+
from librus_python_api.exceptions import (
|
|
19
|
+
ErrorKind,
|
|
20
|
+
LibrusError,
|
|
21
|
+
)
|
|
19
22
|
from librus_python_api.lifecycle import join_owned
|
|
20
23
|
from librus_python_api.models import (
|
|
21
24
|
AttachmentHeaders,
|
|
@@ -283,9 +286,7 @@ class ModernAttachmentStream(AttachmentStream):
|
|
|
283
286
|
budget,
|
|
284
287
|
)
|
|
285
288
|
except BaseException as error:
|
|
286
|
-
client.
|
|
287
|
-
if isinstance(error, SessionExpiredError):
|
|
288
|
-
error._messages_origin = True
|
|
289
|
+
client._modern_read_failed(error)
|
|
289
290
|
raise
|
|
290
291
|
await client._transport.stream_download(
|
|
291
292
|
key,
|
|
@@ -31,6 +31,7 @@ from pydantic import (
|
|
|
31
31
|
model_validator,
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
+
from librus_python_api import __version__
|
|
34
35
|
from librus_python_api.exceptions import ErrorKind, LibrusError
|
|
35
36
|
from librus_python_api.models import (
|
|
36
37
|
AttendanceView,
|
|
@@ -113,7 +114,7 @@ UPSTREAM_ORIGINS = MappingProxyType(
|
|
|
113
114
|
OAUTH_QUERY = (("client_id", "46"),)
|
|
114
115
|
SESSION_COOKIE = "oauth_token"
|
|
115
116
|
AUTH_COOKIES = frozenset({SESSION_COOKIE, "DZIENNIKSID", "SDZIENNIKSID"})
|
|
116
|
-
USER_AGENT = "librus-python-api/
|
|
117
|
+
USER_AGENT = f"librus-python-api/{__version__} (independent client)"
|
|
117
118
|
ENDPOINTS: Mapping[str, Endpoint] = MappingProxyType(
|
|
118
119
|
{
|
|
119
120
|
item.operation_id: item
|
|
@@ -435,7 +436,7 @@ ENDPOINTS: Mapping[str, Endpoint] = MappingProxyType(
|
|
|
435
436
|
),
|
|
436
437
|
# Gateway reads behind the grades page's new-schema descriptive
|
|
437
438
|
# table. Routes and field names come from public client code and the
|
|
438
|
-
# official page script
|
|
439
|
+
# official page script; one authorized read parsed four records.
|
|
439
440
|
Endpoint(
|
|
440
441
|
"descriptive_token_info",
|
|
441
442
|
"GET",
|
|
@@ -862,9 +863,11 @@ FORMATIVE_TEMPLATE_ID = "000000"
|
|
|
862
863
|
FORMATIVE_MAX_TEXT_LENGTH = 65536
|
|
863
864
|
ATTENDANCE_DETAIL_MAX_FIELDS = 32
|
|
864
865
|
ATTENDANCE_METADATA_CACHE_SIZE = 256
|
|
865
|
-
ATTENDANCE_RESULT_CACHE_SIZE = 64
|
|
866
866
|
ATTENDANCE_METADATA_TTL_SECONDS = 3600
|
|
867
867
|
|
|
868
|
+
# Entries of every cached read of one account, oldest first out.
|
|
869
|
+
ACCOUNT_RESULT_CACHE_SIZE = 64
|
|
870
|
+
|
|
868
871
|
ANNOUNCEMENT_MAX_ITEMS = 256
|
|
869
872
|
ANNOUNCEMENT_MAX_FIELD_LENGTH = 1024
|
|
870
873
|
ANNOUNCEMENT_MAX_CONTENT_LENGTH = 65536
|
|
@@ -136,7 +136,9 @@ class OperationTimeoutError(LibrusError):
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
class LimitError(LibrusError):
|
|
139
|
-
|
|
139
|
+
# Library-private: the bound was the cookie count and the jar was emptied,
|
|
140
|
+
# so the session behind it is gone.
|
|
141
|
+
_cookies_cleared = False
|
|
140
142
|
|
|
141
143
|
|
|
142
144
|
class ParseError(LibrusError):
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
The grades page of a school on the new descriptive grading schema renders a
|
|
4
4
|
subject table whose semester cells are empty; the page script then loads the
|
|
5
5
|
grades from gateway JSON routes. Routes and field names come from public client
|
|
6
|
-
code and the official page script
|
|
7
|
-
|
|
6
|
+
code and the official page script. One authorized read on 2026-10-28 parsed four
|
|
7
|
+
PartialGrades records on one school; pagination keys are still unknown, so every
|
|
8
|
+
unexpected shape fails instead of producing a partial collection.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
import re
|
|
@@ -34,11 +34,11 @@ from librus_python_api.completed_lessons import (
|
|
|
34
34
|
validate_selection,
|
|
35
35
|
)
|
|
36
36
|
from librus_python_api.config import (
|
|
37
|
+
ACCOUNT_RESULT_CACHE_SIZE,
|
|
37
38
|
ATTACHMENT_MAX_BYTES,
|
|
38
39
|
ATTENDANCE_MAX_WINDOW_DAYS,
|
|
39
40
|
ATTENDANCE_METADATA_CACHE_SIZE,
|
|
40
41
|
ATTENDANCE_METADATA_TTL_SECONDS,
|
|
41
|
-
ATTENDANCE_RESULT_CACHE_SIZE,
|
|
42
42
|
CHECKPOINT_TIMEOUT_SECONDS,
|
|
43
43
|
ENDPOINTS,
|
|
44
44
|
GRADE_MAX_RECORDS,
|
|
@@ -74,6 +74,7 @@ from librus_python_api.diagnostics import DiagnosticSink, failure_frames
|
|
|
74
74
|
from librus_python_api.exceptions import (
|
|
75
75
|
ErrorKind,
|
|
76
76
|
LibrusError,
|
|
77
|
+
LimitError,
|
|
77
78
|
ParseError,
|
|
78
79
|
SessionExpiredError,
|
|
79
80
|
)
|
|
@@ -1308,7 +1309,7 @@ class AccountClient:
|
|
|
1308
1309
|
budget: RequestBudget | None = None,
|
|
1309
1310
|
max_age_seconds: float = 0.0,
|
|
1310
1311
|
) -> Grades:
|
|
1311
|
-
"""Select a grade view with one
|
|
1312
|
+
"""Select a grade view with one view-changing POST.
|
|
1312
1313
|
|
|
1313
1314
|
Changes the selected grade filter, not school records. A cache hit makes
|
|
1314
1315
|
no POST. Missing inline weight/count data remains unknown. In the all
|
|
@@ -1401,7 +1402,7 @@ class AccountClient:
|
|
|
1401
1402
|
budget: RequestBudget | None = None,
|
|
1402
1403
|
max_age_seconds: float = 0.0,
|
|
1403
1404
|
) -> Attendance:
|
|
1404
|
-
"""Read a fixed upstream attendance view
|
|
1405
|
+
"""Read a fixed upstream attendance view with one view-changing POST."""
|
|
1405
1406
|
form = attendance_view_form(view)
|
|
1406
1407
|
|
|
1407
1408
|
async def fetch(budget: RequestBudget, _: bool) -> Attendance:
|
|
@@ -1540,7 +1541,7 @@ class AccountClient:
|
|
|
1540
1541
|
budget: RequestBudget | None = None,
|
|
1541
1542
|
max_age_seconds: float = 0.0,
|
|
1542
1543
|
) -> Timetable:
|
|
1543
|
-
"""Select an explicit school civil week
|
|
1544
|
+
"""Select an explicit school civil week with one view-changing POST."""
|
|
1544
1545
|
form = timetable_form(monday)
|
|
1545
1546
|
|
|
1546
1547
|
async def fetch(budget: RequestBudget, _: bool) -> Timetable:
|
|
@@ -2143,21 +2144,22 @@ class AccountClient:
|
|
|
2143
2144
|
)
|
|
2144
2145
|
raise
|
|
2145
2146
|
self._cache[key] = (time.monotonic(), result)
|
|
2146
|
-
if len(self._cache) >
|
|
2147
|
+
if len(self._cache) > ACCOUNT_RESULT_CACHE_SIZE:
|
|
2147
2148
|
del self._cache[next(iter(self._cache))]
|
|
2148
2149
|
return result
|
|
2149
2150
|
|
|
2150
2151
|
async def _authenticated[T](
|
|
2151
2152
|
self, fetch: Fetch[T], budget: RequestBudget, retry_safe: bool
|
|
2152
2153
|
) -> T:
|
|
2153
|
-
"""Run a read in a session; recover
|
|
2154
|
+
"""Run a read in a session; recover expiry once where a repeat is safe.
|
|
2154
2155
|
|
|
2155
2156
|
Initial login is never retried: a failed credential submission must not
|
|
2156
|
-
be replayed. A
|
|
2157
|
-
|
|
2158
|
-
logged-out page proves the request
|
|
2159
|
-
|
|
2160
|
-
|
|
2157
|
+
be replayed. A retry-safe read is repeated once after a new login, and
|
|
2158
|
+
so is a read without an account effect (`NONE` or `SELECT_VIEW`, never
|
|
2159
|
+
mark-read) answered with a logged-out page, which proves the request
|
|
2160
|
+
was not applied. Other expiry only clears the session so a later
|
|
2161
|
+
explicit call can log in again. A session just signed in that gets the
|
|
2162
|
+
logged-out page fails as PARSE.
|
|
2161
2163
|
"""
|
|
2162
2164
|
fresh_login = self._identity is None
|
|
2163
2165
|
if fresh_login:
|
|
@@ -2231,9 +2233,7 @@ class AccountClient:
|
|
|
2231
2233
|
raise expired from error
|
|
2232
2234
|
except BaseException as error:
|
|
2233
2235
|
if ENDPOINTS[endpoint].origin == "messages":
|
|
2234
|
-
self.
|
|
2235
|
-
if isinstance(error, SessionExpiredError):
|
|
2236
|
-
error._messages_origin = True
|
|
2236
|
+
self._modern_read_failed(error)
|
|
2237
2237
|
raise
|
|
2238
2238
|
|
|
2239
2239
|
def _check_cooldown(self, key: str) -> None:
|
|
@@ -2249,6 +2249,22 @@ class AccountClient:
|
|
|
2249
2249
|
self._metadata_maps.clear()
|
|
2250
2250
|
self._transport.clear_auth()
|
|
2251
2251
|
|
|
2252
|
+
def _modern_read_failed(self, error: BaseException) -> None:
|
|
2253
|
+
"""Drop the modern session unless the failure says nothing about it.
|
|
2254
|
+
|
|
2255
|
+
A cancel or a local bound keeps it. Every other failure drops it, an
|
|
2256
|
+
unrecognized body included: a session the upstream no longer knows may
|
|
2257
|
+
be answered with one, and keeping it would fail every later read, while
|
|
2258
|
+
dropping it costs one launch.
|
|
2259
|
+
"""
|
|
2260
|
+
if isinstance(error, asyncio.CancelledError) or (
|
|
2261
|
+
isinstance(error, LimitError) and not error._cookies_cleared
|
|
2262
|
+
):
|
|
2263
|
+
return
|
|
2264
|
+
self._invalidate_modern()
|
|
2265
|
+
if isinstance(error, SessionExpiredError):
|
|
2266
|
+
error._messages_origin = True
|
|
2267
|
+
|
|
2252
2268
|
def _invalidate_modern(self) -> None:
|
|
2253
2269
|
self._modern_account = None
|
|
2254
2270
|
for key in tuple(self._cache):
|
|
@@ -50,7 +50,12 @@ from librus_python_api.config import (
|
|
|
50
50
|
recipient_form,
|
|
51
51
|
validate_modern_query,
|
|
52
52
|
)
|
|
53
|
-
from librus_python_api.exceptions import
|
|
53
|
+
from librus_python_api.exceptions import (
|
|
54
|
+
ErrorKind,
|
|
55
|
+
LibrusError,
|
|
56
|
+
LimitError,
|
|
57
|
+
SessionExpiredError,
|
|
58
|
+
)
|
|
54
59
|
from librus_python_api.models import (
|
|
55
60
|
AttachmentHeaders,
|
|
56
61
|
LoginSubmission,
|
|
@@ -451,9 +456,7 @@ class AiohttpTransport:
|
|
|
451
456
|
proxy=proxy.get_secret_value() if proxy is not None else None,
|
|
452
457
|
) as response:
|
|
453
458
|
self._check_headers(response)
|
|
454
|
-
|
|
455
|
-
session.cookie_jar.clear()
|
|
456
|
-
raise LibrusError(ErrorKind.LIMIT)
|
|
459
|
+
self._check_cookies(session)
|
|
457
460
|
if not modern or response.status not in (400, 422):
|
|
458
461
|
self._check_status(response)
|
|
459
462
|
body = await self._read_body(response, budget)
|
|
@@ -542,9 +545,7 @@ class AiohttpTransport:
|
|
|
542
545
|
proxy=proxy.get_secret_value() if proxy is not None else None,
|
|
543
546
|
) as response:
|
|
544
547
|
self._check_headers(response)
|
|
545
|
-
|
|
546
|
-
session.cookie_jar.clear()
|
|
547
|
-
raise LibrusError(ErrorKind.LIMIT)
|
|
548
|
+
self._check_cookies(session)
|
|
548
549
|
self._check_status(response)
|
|
549
550
|
body = await self._read_payload(response, budget)
|
|
550
551
|
if response.status == 200:
|
|
@@ -843,9 +844,7 @@ class AiohttpTransport:
|
|
|
843
844
|
proxy=proxy.get_secret_value() if proxy is not None else None,
|
|
844
845
|
) as response:
|
|
845
846
|
self._check_headers(response)
|
|
846
|
-
|
|
847
|
-
session.cookie_jar.clear()
|
|
848
|
-
raise LibrusError(ErrorKind.LIMIT)
|
|
847
|
+
self._check_cookies(session)
|
|
849
848
|
self._check_status(response)
|
|
850
849
|
body = await self._read_body(response, budget)
|
|
851
850
|
return TransportResponse(
|
|
@@ -863,6 +862,13 @@ class AiohttpTransport:
|
|
|
863
862
|
):
|
|
864
863
|
raise LibrusError(ErrorKind.LIMIT)
|
|
865
864
|
|
|
865
|
+
def _check_cookies(self, session: aiohttp.ClientSession) -> None:
|
|
866
|
+
if len(session.cookie_jar) > self._limits.max_cookies:
|
|
867
|
+
session.cookie_jar.clear()
|
|
868
|
+
error = LimitError(ErrorKind.LIMIT)
|
|
869
|
+
error._cookies_cleared = True
|
|
870
|
+
raise error
|
|
871
|
+
|
|
866
872
|
def _check_status(self, response: aiohttp.ClientResponse) -> None:
|
|
867
873
|
if response.status in (429, 503):
|
|
868
874
|
pause = self._limits.cooldown_seconds
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/_notification_bootstrap.py
RENAMED
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/_notification_codec.py
RENAMED
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/_windows_filesystem.py
RENAMED
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/attachment_routes.py
RENAMED
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/attendance_frequency.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/class_free_days.py
RENAMED
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/completed_lessons.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/formative_grades.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/message_content.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/modern_messages.py
RENAMED
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/notification_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/notification_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.8.1 → librus_python_api-1.8.2}/src/librus_python_api/school_year_archive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|