librus-python-api 1.4.0__tar.gz → 1.5.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.
Files changed (55) hide show
  1. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/API.md +6 -3
  2. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/CHANGELOG.md +14 -0
  3. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/PKG-INFO +6 -6
  4. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/README.md +5 -5
  5. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/__init__.py +1 -1
  6. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/message_content.py +18 -11
  7. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/models.py +2 -0
  8. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/.gitignore +0 -0
  9. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/LICENSE +0 -0
  10. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/SECURITY.md +0 -0
  11. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/pyproject.toml +0 -0
  12. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/_notification_bootstrap.py +0 -0
  13. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/_notification_codec.py +0 -0
  14. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/_storage.py +0 -0
  15. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/_windows_filesystem.py +0 -0
  16. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/announcements.py +0 -0
  17. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/attachment_routes.py +0 -0
  18. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/attachments.py +0 -0
  19. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/attendance.py +0 -0
  20. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/attendance_frequency.py +0 -0
  21. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/budget.py +0 -0
  22. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/checkpoint.py +0 -0
  23. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/completed_lessons.py +0 -0
  24. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/config.py +0 -0
  25. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/detail_fields.py +0 -0
  26. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/diagnostics.py +0 -0
  27. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/exceptions.py +0 -0
  28. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/files.py +0 -0
  29. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/formative_grades.py +0 -0
  30. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/grade_parsers.py +0 -0
  31. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/grade_records.py +0 -0
  32. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/homework_range.py +0 -0
  33. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/lifecycle.py +0 -0
  34. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/markup.py +0 -0
  35. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/messages.py +0 -0
  36. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/modern_body.py +0 -0
  37. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/modern_mailbox.py +0 -0
  38. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/modern_messages.py +0 -0
  39. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/notification_models.py +0 -0
  40. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/notification_persistence.py +0 -0
  41. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/notification_workflow.py +0 -0
  42. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/notifications.py +0 -0
  43. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/parsers.py +0 -0
  44. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/parsing.py +0 -0
  45. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/persistence.py +0 -0
  46. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/py.typed +0 -0
  47. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/recipients.py +0 -0
  48. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/scheduler.py +0 -0
  49. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/school_reads.py +0 -0
  50. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/school_year_archive.py +0 -0
  51. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/sending.py +0 -0
  52. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/service.py +0 -0
  53. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/timetable.py +0 -0
  54. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/src/librus_python_api/transport.py +0 -0
  55. {librus_python_api-1.4.0 → librus_python_api-1.5.0}/uv.lock +0 -0
@@ -883,12 +883,15 @@ Foreign accounts, arbitrary URLs and injected IDs fail before authentication.
883
883
  `Przeczytano` value; it is not inbox unread or per-recipient status.
884
884
  - Sent content may omit an `Adresat` field: `correspondent=None` then preserves
885
885
  absence rather than inventing a name from another table. `recipient_receipts`
886
- holds ordered `MessageRecipientReceipt(recipient, raw_status, read_timestamp)`
887
- records when a separate individual receipt table is displayed. Equal labels
886
+ holds ordered `MessageRecipientReceipt(recipient, raw_status, read_timestamp,
887
+ recipient_class)` records when a separate individual receipt table is displayed.
888
+ `recipient_class` (1.5.0) keeps the displayed class label when the table has the
889
+ middle class column, and is `None` without it or when that cell is empty.
890
+ Rows of one table must share one layout. Equal labels
888
891
  survive; no recipient IDs or aggregate read time are invented. `NIE` has no
889
892
  read timestamp; displayed dates retain school civil time. Unknown statuses fail.
890
893
  At most 256 receipt rows, 4,096 characters per field and 128 KiB total receipt
891
- text are accepted. An absent table means no reported receipts, not zero recipients.
894
+ text (class labels included) are accepted. An absent table means no reported receipts, not zero recipients.
892
895
  - `text` is full plain text with supported block/`br` boundaries and normalized
893
896
  whitespace, at most 65,536 characters. No HTML, scripts or external resources
894
897
  are returned or fetched. Active body content is unsupported, not executed.
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.0 (2026-10-08) - Sent receipts with a recipient class column
4
+
5
+ Compatible minor release. Existing fields, results and the two-column receipt
6
+ layout are unchanged.
7
+
8
+ - Read sent-message individual receipt tables that add a middle class-label
9
+ column (recipient, class, status). Such a sent open previously failed with
10
+ `UNSUPPORTED_CAPABILITY` (#60).
11
+ - Add `MessageRecipientReceipt.recipient_class` (default `None`, hidden from
12
+ repr): the displayed class label, or `None` without that column or when its
13
+ cell is empty. Consumers serializing receipts field by field see one new key.
14
+ - Rows of one receipt table must share one layout; mixed or other widths still
15
+ fail. Class labels count toward the existing receipt text bounds.
16
+
3
17
  ## 1.4.0 (2026-10-08) - Formative assessments and the observation card
4
18
 
5
19
  Compatible minor release. Existing fields, results and stored notification
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: librus-python-api
3
- Version: 1.4.0
3
+ Version: 1.5.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,9 +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.4.0` adds formative assessments, including the observation card,
46
- to grade results. The 1.3.0 archive read passed installed live checks for
47
- populated and empty archives on two parent logins; see [verification](VERIFICATION.md).
45
+ **Status:** `1.5.0` reads sent-message receipt tables that show a recipient
46
+ class column. `1.4.0` added formative assessments, including the observation
47
+ card, to grade results; see [verification](VERIFICATION.md).
48
48
  The stable 1.x API does not guarantee every school layout. School features depend
49
49
  on what each account can access. See the [compatibility policy](API.md#compatibility-policy).
50
50
  See [limitations](#supported-features-and-limitations) below.
@@ -54,7 +54,7 @@ See [limitations](#supported-features-and-limitations) below.
54
54
  Install the exact release from PyPI after its gated publication completes:
55
55
 
56
56
  ```sh
57
- python -m pip install librus-python-api==1.4.0
57
+ python -m pip install librus-python-api==1.5.0
58
58
  ```
59
59
 
60
60
  Pin the exact version qualified by your application. Before publication, use a
@@ -70,7 +70,7 @@ python -m pip install .
70
70
  To install a locally built wheel instead:
71
71
 
72
72
  ```sh
73
- python -m pip install ./dist/librus_python_api-1.4.0-py3-none-any.whl
73
+ python -m pip install ./dist/librus_python_api-1.5.0-py3-none-any.whl
74
74
  ```
75
75
 
76
76
  No CLI or background process is installed: import the library in your own program.
@@ -16,9 +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.4.0` adds formative assessments, including the observation card,
20
- to grade results. The 1.3.0 archive read passed installed live checks for
21
- populated and empty archives on two parent logins; see [verification](VERIFICATION.md).
19
+ **Status:** `1.5.0` reads sent-message receipt tables that show a recipient
20
+ class column. `1.4.0` added formative assessments, including the observation
21
+ card, to grade results; see [verification](VERIFICATION.md).
22
22
  The stable 1.x API does not guarantee every school layout. School features depend
23
23
  on what each account can access. See the [compatibility policy](API.md#compatibility-policy).
24
24
  See [limitations](#supported-features-and-limitations) below.
@@ -28,7 +28,7 @@ See [limitations](#supported-features-and-limitations) below.
28
28
  Install the exact release from PyPI after its gated publication completes:
29
29
 
30
30
  ```sh
31
- python -m pip install librus-python-api==1.4.0
31
+ python -m pip install librus-python-api==1.5.0
32
32
  ```
33
33
 
34
34
  Pin the exact version qualified by your application. Before publication, use a
@@ -44,7 +44,7 @@ python -m pip install .
44
44
  To install a locally built wheel instead:
45
45
 
46
46
  ```sh
47
- python -m pip install ./dist/librus_python_api-1.4.0-py3-none-any.whl
47
+ python -m pip install ./dist/librus_python_api-1.5.0-py3-none-any.whl
48
48
  ```
49
49
 
50
50
  No CLI or background process is installed: import the library in your own program.
@@ -136,7 +136,7 @@ from librus_python_api.models import (
136
136
  from librus_python_api.sending import SendAttempt
137
137
  from librus_python_api.service import AccountClient, LibrusService
138
138
 
139
- __version__ = "1.4.0"
139
+ __version__ = "1.5.0"
140
140
 
141
141
  __all__ = [
142
142
  "ArchiveAbsences",
@@ -57,26 +57,33 @@ def _recipient_receipts(
57
57
  raise LibrusError(ErrorKind.UNSUPPORTED_CAPABILITY)
58
58
  result = []
59
59
  total = 0
60
+ # Rows are recipient/status, or recipient/class/status when the page adds
61
+ # the class-label column; one table never mixes the two.
62
+ width = len(cells(table_rows[1]))
60
63
  for row in table_rows[1:]:
61
64
  columns = cells(row)
62
- if len(columns) != 2 or any(
63
- c.tag != "td"
64
- or c.get("colspan", "1") != "1"
65
- or c.get("rowspan", "1") != "1"
66
- or next(c.iterdescendants("table"), None) is not None
67
- for c in columns
65
+ if (
66
+ len(columns) != width
67
+ or width not in (2, 3)
68
+ or any(
69
+ c.tag != "td"
70
+ or c.get("colspan", "1") != "1"
71
+ or c.get("rowspan", "1") != "1"
72
+ or next(c.iterdescendants("table"), None) is not None
73
+ for c in columns
74
+ )
68
75
  ):
69
76
  raise LibrusError(ErrorKind.UNSUPPORTED_CAPABILITY)
70
- recipient, status = [
71
- text(c, MESSAGE_MAX_FIELD_LENGTH, multiline=True) for c in columns
72
- ]
77
+ values = [text(c, MESSAGE_MAX_FIELD_LENGTH, multiline=True) for c in columns]
78
+ recipient, status = values[0], values[-1]
73
79
  if not recipient or not status:
74
80
  raise LibrusError(ErrorKind.PARSE)
75
- total += len(recipient) + len(status)
81
+ total += sum(map(len, values))
76
82
  if total > MESSAGE_MAX_RECEIPT_TEXT_LENGTH:
77
83
  raise LibrusError(ErrorKind.LIMIT)
78
84
  stamp = None if status == "NIE" else _timestamp(status)
79
- result.append(MessageRecipientReceipt(recipient, status, stamp))
85
+ label = values[1] if width == 3 and values[1] else None
86
+ result.append(MessageRecipientReceipt(recipient, status, stamp, label))
80
87
  return tuple(result)
81
88
 
82
89
 
@@ -210,6 +210,8 @@ class MessageRecipientReceipt:
210
210
  recipient: str = field(repr=False)
211
211
  raw_status: str = field(repr=False)
212
212
  read_timestamp: MessageTimestamp | None = field(repr=False)
213
+ # Displayed class label of the optional middle column; None without it.
214
+ recipient_class: str | None = field(default=None, repr=False)
213
215
 
214
216
 
215
217
  @dataclass(frozen=True, slots=True)