librus-python-api 1.6.2__tar.gz → 1.6.4__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.6.2 → librus_python_api-1.6.4}/API.md +2 -2
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/CHANGELOG.md +42 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/PKG-INFO +8 -8
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/README.md +6 -6
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/SECURITY.md +11 -1
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/pyproject.toml +1 -1
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/__init__.py +1 -1
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/modern_body.py +33 -6
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/uv.lock +35 -35
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/.gitignore +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/LICENSE +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/_notification_bootstrap.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/_notification_codec.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/_storage.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/_windows_filesystem.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/announcements.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/attachment_routes.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/attachments.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/attendance.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/attendance_frequency.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/budget.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/checkpoint.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/class_free_days.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/completed_lessons.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/config.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/detail_fields.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/diagnostics.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/exceptions.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/files.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/formative_grades.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/grade_parsers.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/grade_records.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/homework_range.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/lifecycle.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/markup.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/message_content.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/messages.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/models.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/modern_mailbox.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/modern_messages.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/notification_models.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/notification_persistence.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/notification_workflow.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/notifications.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/parsers.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/parsing.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/persistence.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/py.typed +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/recipients.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/scheduler.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/school_reads.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/school_year_archive.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/sending.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/service.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/timetable.py +0 -0
- {librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/transport.py +0 -0
|
@@ -657,7 +657,7 @@ creates state, reads old MCP files or starts background recovery.
|
|
|
657
657
|
files and unsafe POSIX permissions or Windows ACLs fail closed, never reset state.
|
|
658
658
|
No automatic retry, reconciliation, deletion of consumed history or polling.
|
|
659
659
|
- Defaults: 8 admitted storage workers and 8 send workflows, 256 total records,
|
|
660
|
-
32 unused previews, 300-second expiry and
|
|
660
|
+
32 unused previews, 300-second expiry and 1-second SQLite busy timeout.
|
|
661
661
|
Configured maxima: 64 each, 4,096 records, 256 previews, 300 seconds and 5 seconds.
|
|
662
662
|
Storage workers are serialized per store; full queues/contention fail immediately
|
|
663
663
|
or after the bounded busy interval. The final outcome save after a claim waits
|
|
@@ -760,7 +760,7 @@ async with NotificationStore(Path("/absolute/private/notification-state")) as st
|
|
|
760
760
|
state without HTTP. Pending batches do not mark IDs seen. Defaults are bounded:
|
|
761
761
|
16 contexts, 8 workers/workflows, 32 raw/reservation records, 16 MiB checkpoint
|
|
762
762
|
bytes, 4 MiB total seen-state bytes, 4,096 IDs/category, 500 items/1 MiB per batch,
|
|
763
|
-
500 events/128 KiB value JSON per replay slice,
|
|
763
|
+
500 events/128 KiB value JSON per replay slice, 1 s busy timeout, and 5 s
|
|
764
764
|
`final_busy_timeout_seconds` for checkpoint, staging and acknowledgement saves
|
|
765
765
|
that follow upstream work, since all contexts share one write lock. The full
|
|
766
766
|
envelope is capped at 4 MiB encoded body plus 4 MiB metadata; database main file
|
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.4 (2026-10-26) - Message body rendering security fix
|
|
4
|
+
|
|
5
|
+
Fixes a denial of service in message body rendering. Public types, signatures
|
|
6
|
+
and parser behaviour are unchanged.
|
|
7
|
+
|
|
8
|
+
- Security (GHSA-c9qq-qqr2-hf23, CWE-1333): `modern_message_content` decided
|
|
9
|
+
whether a body is an XML `Message` wrapper with a regular expression that
|
|
10
|
+
backtracked exponentially over a run of XML comments or processing
|
|
11
|
+
instructions with no wrapper after it. A body of a few hundred bytes, which any
|
|
12
|
+
Librus user can send, blocked the calling thread for minutes; that includes
|
|
13
|
+
every client that renders received messages, such as librus-mcp. A single
|
|
14
|
+
linear scan now makes the same decision: it accepts exactly the inputs the
|
|
15
|
+
expression accepted, so routing between the XML and HTML readers is
|
|
16
|
+
unchanged. All releases up to 1.6.3 are affected.
|
|
17
|
+
- Development lock: virtualenv 21.14.13 fixes CVE-2026-115586, pre-commit 4.7.0
|
|
18
|
+
and ruff 0.17.1. None is a runtime dependency.
|
|
19
|
+
- CI and release workflows use setup-uv 10.3.2 and pypi-publish 1.14.3. The
|
|
20
|
+
publish action up to 1.14.2 trusted the revoked emSign root CA in the
|
|
21
|
+
Sigstore chain used for Trusted Publishing (GHSA-5xjx-3w67-w9x9).
|
|
22
|
+
- SECURITY.md adds GitHub private vulnerability reporting beside email and lists
|
|
23
|
+
the repository protections; Dependabot runs on a fixed weekly schedule.
|
|
24
|
+
|
|
25
|
+
## 1.6.3 (2026-10-22) - Dependency security update
|
|
26
|
+
|
|
27
|
+
Raises dependency floors and tooling pins past published advisories. Public
|
|
28
|
+
types, signatures and parser behaviour are unchanged.
|
|
29
|
+
|
|
30
|
+
- Runtime: require `aiohttp>=3.14.4`. Earlier 3.14 releases are affected by
|
|
31
|
+
GHSA-494j-54xm-rwp3 (a Secure cookie in the shared cookie jar could be sent
|
|
32
|
+
over plain HTTP) and GHSA-gx76-c99x-2c96 (a subdomain could strip the
|
|
33
|
+
host-only flag from a parent-domain cookie), both relevant to the client
|
|
34
|
+
cookie jar, plus server-side advisories that this client does not reach. The
|
|
35
|
+
lock already used 3.14.5; the floor stops a resolver from choosing an affected
|
|
36
|
+
release.
|
|
37
|
+
- Development lock: certifi 2026.10.19 drops the compromised emSign ECC Root CA
|
|
38
|
+
G3 (GHSA-6q48-6c7w-9gw2), and virtualenv 21.14.12 fixes activation-script and
|
|
39
|
+
path quoting advisories. Neither is a runtime dependency.
|
|
40
|
+
- CI and release workflows pin uv 0.13.5 with per-platform checksums. uv 0.12.7
|
|
41
|
+
trusted the same compromised root (GHSA-hq85-5cpj-mr62) and could write
|
|
42
|
+
outside the install prefix on Windows (GHSA-2cv4-cqwr-gwf7). The lockfile
|
|
43
|
+
format revision moves from 3 to 5 with no resolution change.
|
|
44
|
+
|
|
3
45
|
## 1.6.2 (2026-10-19) - School layout variants and behaviour points
|
|
4
46
|
|
|
5
47
|
Fixes for completed-lesson and grade pages that 1.6.1 rejected with
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: librus-python-api
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.4
|
|
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
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
Requires-Python: >=3.13
|
|
19
|
-
Requires-Dist: aiohttp<4,>=3.14.
|
|
19
|
+
Requires-Dist: aiohttp<4,>=3.14.4
|
|
20
20
|
Requires-Dist: lxml<7,>=6.1
|
|
21
21
|
Requires-Dist: pydantic<3,>=2.12
|
|
22
22
|
Requires-Dist: pywin32<400,>=312; sys_platform == 'win32'
|
|
@@ -42,8 +42,9 @@ 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.6.
|
|
46
|
-
|
|
45
|
+
**Status:** `1.6.4` renders message bodies in linear time, so a crafted body can
|
|
46
|
+
no longer stall a client, and keeps the 1.6.2 completed-lesson and grade layouts,
|
|
47
|
+
including behaviour points, which are validated and not reported as grades.
|
|
47
48
|
Class free days, typed capability outcomes and the live-check freshness watchdog
|
|
48
49
|
remain available. See [verification](VERIFICATION.md).
|
|
49
50
|
The stable 1.x API does not guarantee every school layout. School features depend
|
|
@@ -55,7 +56,7 @@ See [limitations](#supported-features-and-limitations) below.
|
|
|
55
56
|
Install the exact release from PyPI after its gated publication completes:
|
|
56
57
|
|
|
57
58
|
```sh
|
|
58
|
-
python -m pip install librus-python-api==1.6.
|
|
59
|
+
python -m pip install librus-python-api==1.6.4
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
Pin the exact version qualified by your application. Before publication, use a
|
|
@@ -71,7 +72,7 @@ python -m pip install .
|
|
|
71
72
|
To install a locally built wheel instead:
|
|
72
73
|
|
|
73
74
|
```sh
|
|
74
|
-
python -m pip install ./dist/librus_python_api-1.6.
|
|
75
|
+
python -m pip install ./dist/librus_python_api-1.6.4-py3-none-any.whl
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
No CLI or background process is installed: import the library in your own program.
|
|
@@ -250,7 +251,7 @@ Completed lessons may be disabled by the school. Behaviour notes are not
|
|
|
250
251
|
implemented. Some recipient, archive and receipt layouts remain
|
|
251
252
|
unqualified; backend acceptance is not proof of delivery. Tests cover supported
|
|
252
253
|
contracts, not every school or role. Detailed coverage is in the
|
|
253
|
-
[verification log][verification]
|
|
254
|
+
[verification log][verification]; open work is tracked in [issues][issues].
|
|
254
255
|
|
|
255
256
|
## Development and support
|
|
256
257
|
|
|
@@ -265,7 +266,6 @@ MIT licensed. See [LICENSE][license].
|
|
|
265
266
|
[api]: https://github.com/krzysztofbury/librus-python-api/blob/main/API.md
|
|
266
267
|
[upgrade]: https://github.com/krzysztofbury/librus-python-api/blob/main/contracts/account-context.md
|
|
267
268
|
[verification]: https://github.com/krzysztofbury/librus-python-api/blob/main/VERIFICATION.md
|
|
268
|
-
[roadmap]: https://github.com/krzysztofbury/librus-python-api/blob/main/TODO.md
|
|
269
269
|
[contributing]: https://github.com/krzysztofbury/librus-python-api/blob/main/CONTRIBUTING.md
|
|
270
270
|
[issues]: https://github.com/krzysztofbury/librus-python-api/issues
|
|
271
271
|
[security]: https://github.com/krzysztofbury/librus-python-api/blob/main/SECURITY.md
|
|
@@ -16,8 +16,9 @@ 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.6.
|
|
20
|
-
|
|
19
|
+
**Status:** `1.6.4` renders message bodies in linear time, so a crafted body can
|
|
20
|
+
no longer stall a client, and keeps the 1.6.2 completed-lesson and grade layouts,
|
|
21
|
+
including behaviour points, which are validated and not reported as grades.
|
|
21
22
|
Class free days, typed capability outcomes and the live-check freshness watchdog
|
|
22
23
|
remain available. See [verification](VERIFICATION.md).
|
|
23
24
|
The stable 1.x API does not guarantee every school layout. School features depend
|
|
@@ -29,7 +30,7 @@ See [limitations](#supported-features-and-limitations) below.
|
|
|
29
30
|
Install the exact release from PyPI after its gated publication completes:
|
|
30
31
|
|
|
31
32
|
```sh
|
|
32
|
-
python -m pip install librus-python-api==1.6.
|
|
33
|
+
python -m pip install librus-python-api==1.6.4
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
Pin the exact version qualified by your application. Before publication, use a
|
|
@@ -45,7 +46,7 @@ python -m pip install .
|
|
|
45
46
|
To install a locally built wheel instead:
|
|
46
47
|
|
|
47
48
|
```sh
|
|
48
|
-
python -m pip install ./dist/librus_python_api-1.6.
|
|
49
|
+
python -m pip install ./dist/librus_python_api-1.6.4-py3-none-any.whl
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
No CLI or background process is installed: import the library in your own program.
|
|
@@ -224,7 +225,7 @@ Completed lessons may be disabled by the school. Behaviour notes are not
|
|
|
224
225
|
implemented. Some recipient, archive and receipt layouts remain
|
|
225
226
|
unqualified; backend acceptance is not proof of delivery. Tests cover supported
|
|
226
227
|
contracts, not every school or role. Detailed coverage is in the
|
|
227
|
-
[verification log][verification]
|
|
228
|
+
[verification log][verification]; open work is tracked in [issues][issues].
|
|
228
229
|
|
|
229
230
|
## Development and support
|
|
230
231
|
|
|
@@ -239,7 +240,6 @@ MIT licensed. See [LICENSE][license].
|
|
|
239
240
|
[api]: https://github.com/krzysztofbury/librus-python-api/blob/main/API.md
|
|
240
241
|
[upgrade]: https://github.com/krzysztofbury/librus-python-api/blob/main/contracts/account-context.md
|
|
241
242
|
[verification]: https://github.com/krzysztofbury/librus-python-api/blob/main/VERIFICATION.md
|
|
242
|
-
[roadmap]: https://github.com/krzysztofbury/librus-python-api/blob/main/TODO.md
|
|
243
243
|
[contributing]: https://github.com/krzysztofbury/librus-python-api/blob/main/CONTRIBUTING.md
|
|
244
244
|
[issues]: https://github.com/krzysztofbury/librus-python-api/issues
|
|
245
245
|
[security]: https://github.com/krzysztofbury/librus-python-api/blob/main/SECURITY.md
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Security
|
|
2
2
|
|
|
3
3
|
Please do not report vulnerabilities or expose school data in public issues.
|
|
4
|
-
|
|
4
|
+
Report vulnerabilities privately through the repository's **Security** tab
|
|
5
|
+
(**Report a vulnerability**) or by email to
|
|
6
|
+
[contact@datacraze.io](mailto:contact@datacraze.io).
|
|
5
7
|
|
|
6
8
|
Include the affected version or commit, steps to reproduce, expected and
|
|
7
9
|
observed behavior, and impact. Redact credentials, context keys, cookies, tokens, and any
|
|
@@ -11,3 +13,11 @@ The [client API security review](contracts/api-security-review.md) maps the
|
|
|
11
13
|
outbound-client threat boundary and existing controls to Snyk's OWASP API guidance.
|
|
12
14
|
It also records consumer authorization/serialization responsibilities and local
|
|
13
15
|
filesystem/persistence limits; it is not a third-party security certification.
|
|
16
|
+
|
|
17
|
+
## Repository protections
|
|
18
|
+
|
|
19
|
+
The default branch requires a pull request and the CI checks, and refuses force
|
|
20
|
+
pushes and deletion. Secret scanning with push protection, Dependabot alerts and
|
|
21
|
+
security updates, CodeQL default setup and private vulnerability reporting are
|
|
22
|
+
enabled. Workflows default to a read-only token and need approval before running
|
|
23
|
+
for outside contributors.
|
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.14",
|
|
22
22
|
"Typing :: Typed",
|
|
23
23
|
]
|
|
24
|
-
dependencies = ["aiohttp>=3.14.
|
|
24
|
+
dependencies = ["aiohttp>=3.14.4,<4", "lxml>=6.1,<7", "pydantic>=2.12,<3", "yarl>=1.20,<2", "pywin32>=312,<400; sys_platform == 'win32'", "tzdata>=2026.5; sys_platform == 'win32'"]
|
|
25
25
|
|
|
26
26
|
[project.urls]
|
|
27
27
|
Repository = "https://github.com/krzysztofbury/librus-python-api"
|
|
@@ -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.6.
|
|
141
|
+
__version__ = "1.6.4"
|
|
142
142
|
|
|
143
143
|
__all__ = [
|
|
144
144
|
"ArchiveAbsences",
|
|
@@ -11,11 +11,8 @@ from librus_python_api.exceptions import ErrorKind, LibrusError
|
|
|
11
11
|
from librus_python_api.markup import text
|
|
12
12
|
from librus_python_api.parsers import parse_html_document
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
r"(?:<\?xml\b|<Message(?=[\s/>]))",
|
|
17
|
-
re.DOTALL,
|
|
18
|
-
)
|
|
14
|
+
_XML_START = re.compile(r"<\?xml\b|<Message(?=[\s/>])")
|
|
15
|
+
_XML_DECLARATION = re.compile(r"<\?xml\b")
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
def render_body(encoded: str) -> str:
|
|
@@ -34,7 +31,7 @@ def render_body(encoded: str) -> str:
|
|
|
34
31
|
decoded = decoded.removeprefix("\ufeff")
|
|
35
32
|
# XML comments/PIs and a UTF-8 BOM must not route a Message wrapper through
|
|
36
33
|
# the permissive HTML renderer, losing CDATA or bypassing Content checks.
|
|
37
|
-
if
|
|
34
|
+
if _xml_prefixed(decoded):
|
|
38
35
|
decoded = _xml_content(decoded)
|
|
39
36
|
if not decoded:
|
|
40
37
|
return ""
|
|
@@ -47,6 +44,36 @@ def render_body(encoded: str) -> str:
|
|
|
47
44
|
)
|
|
48
45
|
|
|
49
46
|
|
|
47
|
+
def _xml_prefixed(decoded: str) -> bool:
|
|
48
|
+
"""Whether whitespace, comments and non-declaration PIs precede an XML start.
|
|
49
|
+
|
|
50
|
+
A comment or PI may end at any later terminator, as the lazy pattern this
|
|
51
|
+
replaces allowed, but one left-to-right pass decides it: once an item has
|
|
52
|
+
opened, every later terminator closes it. A regex backtracks exponentially
|
|
53
|
+
over a run of items with no XML start after it.
|
|
54
|
+
"""
|
|
55
|
+
starts = bytearray(len(decoded) + 1)
|
|
56
|
+
starts[0] = 1
|
|
57
|
+
comment_from = pi_from = len(decoded) + 1
|
|
58
|
+
for index, char in enumerate(decoded):
|
|
59
|
+
if starts[index]:
|
|
60
|
+
if char.isspace():
|
|
61
|
+
starts[index + 1] = 1
|
|
62
|
+
elif _XML_START.match(decoded, index):
|
|
63
|
+
return True
|
|
64
|
+
elif decoded.startswith("<!--", index):
|
|
65
|
+
comment_from = min(comment_from, index + 4)
|
|
66
|
+
elif decoded.startswith("<?", index) and not _XML_DECLARATION.match(
|
|
67
|
+
decoded, index
|
|
68
|
+
):
|
|
69
|
+
pi_from = min(pi_from, index + 2)
|
|
70
|
+
if index >= comment_from and decoded.startswith("-->", index):
|
|
71
|
+
starts[index + 3] = 1
|
|
72
|
+
if index >= pi_from and decoded.startswith("?>", index):
|
|
73
|
+
starts[index + 2] = 1
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
50
77
|
def _xml_content(decoded: str) -> str:
|
|
51
78
|
parser = etree.XMLParser(
|
|
52
79
|
no_network=True,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 5
|
|
3
3
|
requires-python = ">=3.13"
|
|
4
4
|
resolution-markers = [
|
|
5
5
|
"python_full_version >= '3.15'",
|
|
@@ -275,11 +275,11 @@ filecache = [
|
|
|
275
275
|
|
|
276
276
|
[[package]]
|
|
277
277
|
name = "certifi"
|
|
278
|
-
version = "2026.
|
|
278
|
+
version = "2026.10.19"
|
|
279
279
|
source = { registry = "https://pypi.org/simple" }
|
|
280
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
280
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" }
|
|
281
281
|
wheels = [
|
|
282
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
282
|
+
{ url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" },
|
|
283
283
|
]
|
|
284
284
|
|
|
285
285
|
[[package]]
|
|
@@ -553,11 +553,11 @@ wheels = [
|
|
|
553
553
|
|
|
554
554
|
[[package]]
|
|
555
555
|
name = "filelock"
|
|
556
|
-
version = "4.
|
|
556
|
+
version = "4.2.4"
|
|
557
557
|
source = { registry = "https://pypi.org/simple" }
|
|
558
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
558
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/28/3893324bcadf8f89b1616637468d766af3f625008c0735bf704fdfa86617/filelock-4.2.4.tar.gz", hash = "sha256:c6d8663535e1650ee430507b4ffbac7b464dd64b7647ea0112a9f406499d163d", size = 652444, upload-time = "2026-10-19T01:57:51.001Z" }
|
|
559
559
|
wheels = [
|
|
560
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
560
|
+
{ url = "https://files.pythonhosted.org/packages/e0/1b/5e74289a1878e666bb4b65fe5805a04cafcac2aa5f519ef1ead67ef91c56/filelock-4.2.4-py3-none-any.whl", hash = "sha256:301241438c57936ef93f4243a94f2559022268bfcf9b375a9a72eb0f8db8358e", size = 162475, upload-time = "2026-10-19T01:57:49.419Z" },
|
|
561
561
|
]
|
|
562
562
|
|
|
563
563
|
[[package]]
|
|
@@ -931,7 +931,7 @@ dev = [
|
|
|
931
931
|
|
|
932
932
|
[package.metadata]
|
|
933
933
|
requires-dist = [
|
|
934
|
-
{ name = "aiohttp", specifier = ">=3.14.
|
|
934
|
+
{ name = "aiohttp", specifier = ">=3.14.4,<4" },
|
|
935
935
|
{ name = "lxml", specifier = ">=6.1,<7" },
|
|
936
936
|
{ name = "pydantic", specifier = ">=2.12,<3" },
|
|
937
937
|
{ name = "pywin32", marker = "sys_platform == 'win32'", specifier = ">=312,<400" },
|
|
@@ -1518,7 +1518,7 @@ wheels = [
|
|
|
1518
1518
|
|
|
1519
1519
|
[[package]]
|
|
1520
1520
|
name = "pre-commit"
|
|
1521
|
-
version = "4.
|
|
1521
|
+
version = "4.7.0"
|
|
1522
1522
|
source = { registry = "https://pypi.org/simple" }
|
|
1523
1523
|
dependencies = [
|
|
1524
1524
|
{ name = "cfgv" },
|
|
@@ -1527,9 +1527,9 @@ dependencies = [
|
|
|
1527
1527
|
{ name = "pyyaml" },
|
|
1528
1528
|
{ name = "virtualenv" },
|
|
1529
1529
|
]
|
|
1530
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1530
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3a/c3/6965c2690ddce885ec75121572d065a5b94c5a5d8d3e657927ecda9fd715/pre_commit-4.7.0.tar.gz", hash = "sha256:1f3deee12b914c8fa184bd2b5953391c2e4f36763e403d4a23dc9983fa7c6e86", size = 198120, upload-time = "2026-10-12T20:45:40.32Z" }
|
|
1531
1531
|
wheels = [
|
|
1532
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1532
|
+
{ url = "https://files.pythonhosted.org/packages/eb/83/ce0a196e4f7b20b11ce3aa1fc72fb973f64b7628d51c1b8aebad37fb7f81/pre_commit-4.7.0-py2.py3-none-any.whl", hash = "sha256:2e229038ad3656081b70c27913157e4b636ef306f643f024a8a8e3f0f7b2f877", size = 226261, upload-time = "2026-10-12T20:45:38.858Z" },
|
|
1533
1533
|
]
|
|
1534
1534
|
|
|
1535
1535
|
[[package]]
|
|
@@ -2050,27 +2050,27 @@ wheels = [
|
|
|
2050
2050
|
|
|
2051
2051
|
[[package]]
|
|
2052
2052
|
name = "ruff"
|
|
2053
|
-
version = "0.17.
|
|
2054
|
-
source = { registry = "https://pypi.org/simple" }
|
|
2055
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
2056
|
-
wheels = [
|
|
2057
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2058
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2059
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2060
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2061
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2062
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2063
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2064
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2065
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2066
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2067
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2068
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2069
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2070
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2071
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2072
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2073
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2053
|
+
version = "0.17.1"
|
|
2054
|
+
source = { registry = "https://pypi.org/simple" }
|
|
2055
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e7/e6d5749983b020e6cb1055abec0a6f361a8e94f0184cdcf56d4760995f02/ruff-0.17.1.tar.gz", hash = "sha256:5bb796c5112e9fb9527f2ef2faa0a130d6312d19de92bcfaddd70dbb2d41020f", size = 5323349, upload-time = "2026-10-15T15:23:18.64Z" }
|
|
2056
|
+
wheels = [
|
|
2057
|
+
{ url = "https://files.pythonhosted.org/packages/2e/78/90c46ca6250709f7c37e397b60d06061adee8d0f7d13959ed6a8e49b14fd/ruff-0.17.1-py3-none-linux_armv6l.whl", hash = "sha256:1796c41eafec66f4635125dd1e074a7aa27b8d20c187d484360acf11d247fc4a", size = 10147528, upload-time = "2026-10-15T15:22:33.667Z" },
|
|
2058
|
+
{ url = "https://files.pythonhosted.org/packages/aa/d2/a2e28bc35b083ae51fa07dfba5280a667c8b01ecaee7802fc8849dcd5c46/ruff-0.17.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8eadb09a788010be29c895418bdfa513e4e1c65e64b94e5b19e4cd381f6cc7fb", size = 10225974, upload-time = "2026-10-15T15:22:36.328Z" },
|
|
2059
|
+
{ url = "https://files.pythonhosted.org/packages/8e/71/7a9029a81c8206ee01a98f6cfeeb56e6c095af86857581a37045c46d7e94/ruff-0.17.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:29b454f18ca3eeaf37fbf140841dcf27d36109e29dee9aae8acecb68c26c6f74", size = 9270361, upload-time = "2026-10-15T15:22:38.651Z" },
|
|
2060
|
+
{ url = "https://files.pythonhosted.org/packages/c0/8c/e075e9c917cb0575297880c96768304e881240171c870cbd8d9b7b54bec2/ruff-0.17.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872e03dd231744d6c5ec5c8af6be52b71323fc6ee096e4c8b34d9f844ee3b978", size = 9938300, upload-time = "2026-10-15T15:22:41.284Z" },
|
|
2061
|
+
{ url = "https://files.pythonhosted.org/packages/87/27/0b4bb926a49de2383f161272b4d863571a4c7ecdbf595a6bc58813a13367/ruff-0.17.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c0d6cb5f8fbac77eec9af49f4fc0eaf856b05545e372b0694d24b4e76b1c640", size = 9948464, upload-time = "2026-10-15T15:22:43.681Z" },
|
|
2062
|
+
{ url = "https://files.pythonhosted.org/packages/14/27/518d57e0e02aecb8ea4acac6b57b51a53173d25fa734952bfdd7d531e88e/ruff-0.17.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78beced4a3c94dfedbee4b999b95b6a27b35820fdc0afbacc6d37a8da26c1cd1", size = 10638639, upload-time = "2026-10-15T15:22:46.081Z" },
|
|
2063
|
+
{ url = "https://files.pythonhosted.org/packages/01/82/d89a4e498ade59e6cee52d67f8da569019372212b87468c1fdeae884601c/ruff-0.17.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7feefc63921202894f0e158b0e32f45a5950d70a7fb693ccb9264816c25b1c05", size = 11337238, upload-time = "2026-10-15T15:22:48.894Z" },
|
|
2064
|
+
{ url = "https://files.pythonhosted.org/packages/a2/7c/3b0fdef9352656317c977086fe1d802902406e4f2a0d8578da90c0e6d1c5/ruff-0.17.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684154861a819bf1f433669d2a0ce9c3e54677c65b8ebb866cc2a7ec86345785", size = 10896327, upload-time = "2026-10-15T15:22:51.45Z" },
|
|
2065
|
+
{ url = "https://files.pythonhosted.org/packages/62/86/5566290219708ce2928f3a29c4f843a1185b341ec6111c4631a33c5ceced/ruff-0.17.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ef98e51de8ef38082f90653ceeb84944bd7673783eea13e1647c5b9b064189", size = 10350811, upload-time = "2026-10-15T15:22:54.401Z" },
|
|
2066
|
+
{ url = "https://files.pythonhosted.org/packages/ad/d2/ca8462dabb3c413990d49703ba306c30c862195a75448d080fa8fcce9775/ruff-0.17.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8f9aa5b5bd5fa1937803ea609c8f153b8de39bf3e0e149897ae34b0c5b52f467", size = 10569994, upload-time = "2026-10-15T15:22:57.142Z" },
|
|
2067
|
+
{ url = "https://files.pythonhosted.org/packages/e3/ad/dd1864c1f1db17c8cfb297ce70b352388e6977620f66f7182c52855407df/ruff-0.17.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:80e53b0e5faa3c5667f035daaa032893ae42b7cfe708c886d39ed131e54688ab", size = 10059672, upload-time = "2026-10-15T15:23:00.084Z" },
|
|
2068
|
+
{ url = "https://files.pythonhosted.org/packages/65/45/3f56cf1d2168c2165cdd5ac6f9668aee66a4cfdd4caf2b2cc5ccb2b1d568/ruff-0.17.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:056c5d5fa0ac790051ae165d7035c7bf956d02de2db557f2e9b31d877a505fca", size = 9947803, upload-time = "2026-10-15T15:23:03.132Z" },
|
|
2069
|
+
{ url = "https://files.pythonhosted.org/packages/aa/4b/5457cb2be4c1d1ea5e919558131a0f197a4a454a0530d9e30820e8206e4c/ruff-0.17.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd8f5a55b10f371642eacd0e8665a42a8b97da15c9a7187c79e697b77b5e0ce4", size = 10351807, upload-time = "2026-10-15T15:23:05.628Z" },
|
|
2070
|
+
{ url = "https://files.pythonhosted.org/packages/49/fb/db15b4f8fa24a1e36b99e1401f42ef6df16ee299a48670d5d262b771e7d7/ruff-0.17.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ce05e6f3973611d9550a600e04bb531061e368baba31610eb57c1509a8f21a0a", size = 10765716, upload-time = "2026-10-15T15:23:08.092Z" },
|
|
2071
|
+
{ url = "https://files.pythonhosted.org/packages/0a/e8/1e186578076aff7a80735f625005fde678412ab609cd323ed182eb7d6b87/ruff-0.17.1-py3-none-win32.whl", hash = "sha256:1de0030a8aa78c65c5fecb84b2e5c390ac684c0452875a02afc7c083d49a496a", size = 9895093, upload-time = "2026-10-15T15:23:10.582Z" },
|
|
2072
|
+
{ url = "https://files.pythonhosted.org/packages/6a/2e/da3d9589bae8e8b58f3a48bd5bc0b980232fb14a83bf02c3be7660f87c1b/ruff-0.17.1-py3-none-win_amd64.whl", hash = "sha256:5df76f16580fab58d0f6c98a3ac254a4abceb7b2489b8b678335869cff77b3d7", size = 10097955, upload-time = "2026-10-15T15:23:13.309Z" },
|
|
2073
|
+
{ url = "https://files.pythonhosted.org/packages/df/c9/0f2a4dea06a5e12c1191ee3d96b1e1233797d0d5e18f97a2b909a95a9836/ruff-0.17.1-py3-none-win_arm64.whl", hash = "sha256:42400b9a6ff8515ff27796f07f92e0195be011c86e81e381606f58ea5502fef4", size = 9853542, upload-time = "2026-10-15T15:23:16.05Z" },
|
|
2074
2074
|
]
|
|
2075
2075
|
|
|
2076
2076
|
[[package]]
|
|
@@ -2291,7 +2291,7 @@ wheels = [
|
|
|
2291
2291
|
|
|
2292
2292
|
[[package]]
|
|
2293
2293
|
name = "virtualenv"
|
|
2294
|
-
version = "21.14.
|
|
2294
|
+
version = "21.14.13"
|
|
2295
2295
|
source = { registry = "https://pypi.org/simple" }
|
|
2296
2296
|
dependencies = [
|
|
2297
2297
|
{ name = "distlib" },
|
|
@@ -2300,9 +2300,9 @@ dependencies = [
|
|
|
2300
2300
|
{ name = "platformdirs" },
|
|
2301
2301
|
{ name = "python-discovery" },
|
|
2302
2302
|
]
|
|
2303
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
2303
|
+
sdist = { url = "https://files.pythonhosted.org/packages/70/aa/bff50daee0488e16d611258a9b6c5a1d0c9e128f8ca424e9f4e1c0444ef2/virtualenv-21.14.13.tar.gz", hash = "sha256:de5307ba4b9a0001d9c8ea02832d38f63ca432880f416c1bface61c4323fd905", size = 5570301, upload-time = "2026-10-22T20:10:16.952Z" }
|
|
2304
2304
|
wheels = [
|
|
2305
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
2305
|
+
{ url = "https://files.pythonhosted.org/packages/bb/a9/c10d8a44a29fb94a18b43fdf11f38caca53ae7c9406f11a72b1cb8f624f1/virtualenv-21.14.13-py3-none-any.whl", hash = "sha256:5fbb04138a4861580b78ba9110d529a26bc48fcc93c8ec36bc0202eeed96a788", size = 5557094, upload-time = "2026-10-22T20:10:15.096Z" },
|
|
2306
2306
|
]
|
|
2307
2307
|
|
|
2308
2308
|
[[package]]
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/_notification_bootstrap.py
RENAMED
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/_notification_codec.py
RENAMED
|
File without changes
|
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/_windows_filesystem.py
RENAMED
|
File without changes
|
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/attachment_routes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/attendance_frequency.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/class_free_days.py
RENAMED
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/completed_lessons.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/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
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/message_content.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/modern_messages.py
RENAMED
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/src/librus_python_api/notification_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{librus_python_api-1.6.2 → librus_python_api-1.6.4}/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.6.2 → librus_python_api-1.6.4}/src/librus_python_api/school_year_archive.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|