librus-python-api 1.6.2__py3-none-any.whl → 1.6.4__py3-none-any.whl

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.
@@ -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.2"
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
- _XML_PREFIX = re.compile(
15
- r"\s*(?:(?:<!--.*?-->|<\?(?!xml\b).*?\?>)\s*)*"
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 _XML_PREFIX.match(decoded):
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,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: librus-python-api
3
- Version: 1.6.2
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.2
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.2` accepts more completed-lesson and grade layouts, including
46
- behaviour points, which are validated and not reported as grades.
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.2
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.2-py3-none-any.whl
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] and [roadmap][roadmap].
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
@@ -1,4 +1,4 @@
1
- librus_python_api/__init__.py,sha256=ZLWweFJqQFf-SdkQ7pHclXOKI0b-bfqNbNN1u14333w,6513
1
+ librus_python_api/__init__.py,sha256=g55dpGqZUadHtjcfKr1_1nW8ESiC38xEDw2ZNqhr4fw,6513
2
2
  librus_python_api/_notification_bootstrap.py,sha256=18nzH0doXBNaZXCz-_ckIvRrlHTWV5Mm1t5m2-5eYGI,4669
3
3
  librus_python_api/_notification_codec.py,sha256=hU6G3zGJ5fR8wfPC6fV2OqBKs-7XpT9eRnKO3FGsTzY,13451
4
4
  librus_python_api/_storage.py,sha256=M_6ZAzN4zs7laOdKJBRH1I2rvy3pgqYgC_MZtsd2xtM,15893
@@ -26,7 +26,7 @@ librus_python_api/markup.py,sha256=vd483NSUQbVYvSV2IaUmsyGcRhpWtbyDHMeEhbc7siA,5
26
26
  librus_python_api/message_content.py,sha256=v_qoEAoaYS0_9MQBaj57i5XnC7pzv_HHWefgTt4CNyM,9043
27
27
  librus_python_api/messages.py,sha256=K9F8XVs1SojfFpdxxLV8gx9E0g81YxEHjigDzLHmDCg,10230
28
28
  librus_python_api/models.py,sha256=Z6eV-ftjaN8YGUW8SntVTLjGCqe1EWRpd0P1Va8EwDs,40991
29
- librus_python_api/modern_body.py,sha256=3L4P_cIrHdIxZqiinFSQrDMhG6iklT1GoA-VEWMJUiI,2682
29
+ librus_python_api/modern_body.py,sha256=SocXJTyGm1Q_2owzh1PE1zcCB2kqZTz1KPJ2R4yMyoc,3895
30
30
  librus_python_api/modern_mailbox.py,sha256=2jF9UnJZ5AF6dip-GrMcv_z2kSLpp87dIpVeXk9jMzA,20600
31
31
  librus_python_api/modern_messages.py,sha256=qt2CXtrxCZQ-jTt0aQCD6ZaPAJQZXf0BxmvacPo9B0w,10653
32
32
  librus_python_api/notification_models.py,sha256=4eLTAE9JzHhveoaWtZQwGBolmb87QUP5kOSW7Jjt7J0,4199
@@ -45,7 +45,7 @@ librus_python_api/sending.py,sha256=Nvvk2r-6D6Us53a2fH5GW9PcA6bWEfuOvy6c3M5QVcE,
45
45
  librus_python_api/service.py,sha256=7ioLx-8C-I0Qla8YUyu5UqhjBPlcUvEFpQSYH-9yjzs,96232
46
46
  librus_python_api/timetable.py,sha256=tX1QK9fQABxzfLaHHqTe8e6rAgdOkCoTOfAHdPwNmLE,9836
47
47
  librus_python_api/transport.py,sha256=VslHio0FrOiTkISfLNDlGVV_MeGAYidHDKZnoVUIwrI,34310
48
- librus_python_api-1.6.2.dist-info/METADATA,sha256=l317hh6HLcjPf2_EHZN2pVOMuoJvBNqaU8RcBtdhaaU,12070
49
- librus_python_api-1.6.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
50
- librus_python_api-1.6.2.dist-info/licenses/LICENSE,sha256=IuerLb3cnZCsaB23znydjjvKqP-LmSJd3syKa9NGUts,1071
51
- librus_python_api-1.6.2.dist-info/RECORD,,
48
+ librus_python_api-1.6.4.dist-info/METADATA,sha256=1FRSc28E3rv-XOlguzKk2FTBstg_DnhKN2lqZw1CLZw,12103
49
+ librus_python_api-1.6.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
50
+ librus_python_api-1.6.4.dist-info/licenses/LICENSE,sha256=IuerLb3cnZCsaB23znydjjvKqP-LmSJd3syKa9NGUts,1071
51
+ librus_python_api-1.6.4.dist-info/RECORD,,