librus-python-api 1.0.0rc1__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.
Files changed (46) hide show
  1. librus_python_api/__init__.py +243 -0
  2. librus_python_api/_notification_codec.py +310 -0
  3. librus_python_api/_storage.py +367 -0
  4. librus_python_api/announcements.py +159 -0
  5. librus_python_api/attachment_routes.py +114 -0
  6. librus_python_api/attachments.py +297 -0
  7. librus_python_api/attendance.py +182 -0
  8. librus_python_api/attendance_frequency.py +112 -0
  9. librus_python_api/budget.py +79 -0
  10. librus_python_api/checkpoint.py +61 -0
  11. librus_python_api/completed_lessons.py +216 -0
  12. librus_python_api/config.py +1410 -0
  13. librus_python_api/detail_fields.py +50 -0
  14. librus_python_api/diagnostics.py +25 -0
  15. librus_python_api/exceptions.py +172 -0
  16. librus_python_api/files.py +156 -0
  17. librus_python_api/grade_parsers.py +169 -0
  18. librus_python_api/grade_records.py +454 -0
  19. librus_python_api/homework_range.py +41 -0
  20. librus_python_api/lifecycle.py +24 -0
  21. librus_python_api/markup.py +147 -0
  22. librus_python_api/message_content.py +230 -0
  23. librus_python_api/messages.py +288 -0
  24. librus_python_api/models.py +1160 -0
  25. librus_python_api/modern_body.py +75 -0
  26. librus_python_api/modern_mailbox.py +459 -0
  27. librus_python_api/modern_messages.py +276 -0
  28. librus_python_api/notification_models.py +67 -0
  29. librus_python_api/notification_persistence.py +1044 -0
  30. librus_python_api/notification_workflow.py +303 -0
  31. librus_python_api/notifications.py +216 -0
  32. librus_python_api/parsers.py +232 -0
  33. librus_python_api/parsing.py +49 -0
  34. librus_python_api/persistence.py +405 -0
  35. librus_python_api/py.typed +0 -0
  36. librus_python_api/recipients.py +271 -0
  37. librus_python_api/scheduler.py +287 -0
  38. librus_python_api/school_reads.py +400 -0
  39. librus_python_api/sending.py +125 -0
  40. librus_python_api/service.py +2285 -0
  41. librus_python_api/timetable.py +261 -0
  42. librus_python_api/transport.py +956 -0
  43. librus_python_api-1.0.0rc1.dist-info/METADATA +254 -0
  44. librus_python_api-1.0.0rc1.dist-info/RECORD +46 -0
  45. librus_python_api-1.0.0rc1.dist-info/WHEEL +4 -0
  46. librus_python_api-1.0.0rc1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,254 @@
1
+ Metadata-Version: 2.5
2
+ Name: librus-python-api
3
+ Version: 1.0.0rc1
4
+ Summary: Independent bounded async Librus Synergia client
5
+ Project-URL: Repository, https://github.com/krzysztofbury/librus-python-api
6
+ Project-URL: Issues, https://github.com/krzysztofbury/librus-python-api/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Framework :: AsyncIO
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: MacOS :: MacOS X
13
+ Classifier: Operating System :: POSIX
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.13
18
+ Requires-Dist: aiohttp<4,>=3.14.2
19
+ Requires-Dist: lxml<7,>=6.1
20
+ Requires-Dist: pydantic<3,>=2.12
21
+ Requires-Dist: yarl<2,>=1.20
22
+ Description-Content-Type: text/markdown
23
+
24
+ # librus-python-api
25
+
26
+ Read grades, attendance, homework, timetables and messages from **Librus Synergia**
27
+ in Python. This independent, asynchronous client handles login, session recovery,
28
+ pagination and request limits, returning typed Python objects rather than HTML.
29
+
30
+ Use it in personal scripts, notification services or application backends. One
31
+ service can manage multiple logins while keeping their sessions and data separate.
32
+ It is not an official Librus product.
33
+
34
+ **Requirements:** Python 3.13 or newer. Linux and macOS are tested in CI.
35
+ Persistence and saving attachments require POSIX filesystem features, such as
36
+ those on Linux or macOS; Windows disk workflows are unsupported.
37
+
38
+ **Status:** `1.0.0rc1`, a library-only beta prerelease, not stable 1.0 or an MCP
39
+ cutover. MCP integration, legacy-state migration and broader live qualification
40
+ remain unfinished. School features depend on what each account can access.
41
+ See [limitations](#supported-features-and-limitations) below.
42
+
43
+ ## Install
44
+
45
+ Install the exact candidate from PyPI once its gated publication completes:
46
+
47
+ ```sh
48
+ python -m pip install librus-python-api==1.0.0rc1
49
+ ```
50
+
51
+ An explicit version selects the prerelease without opting into every prerelease
52
+ dependency. Before publication, use a checkout or locally built wheel instead.
53
+ From a checkout, install into a virtual environment:
54
+
55
+ ```sh
56
+ python3 -m venv .venv
57
+ . .venv/bin/activate
58
+ python -m pip install .
59
+ ```
60
+
61
+ To install a locally built wheel instead:
62
+
63
+ ```sh
64
+ python -m pip install ./dist/librus_python_api-1.0.0rc1-py3-none-any.whl
65
+ ```
66
+
67
+ No CLI or background process is installed: import the library in your own program.
68
+
69
+ ## First request
70
+
71
+ Provide your login and password through your application's secret management.
72
+ The example reads environment variables; the library itself does not discover
73
+ environment variables or credential files.
74
+
75
+ You also need an application context key. Generate it **once**, store it alongside
76
+ your other application secrets, and reuse it across runs:
77
+
78
+ ```sh
79
+ python -c 'import secrets; print(secrets.token_hex(32))'
80
+ ```
81
+
82
+ Set `LIBRUS_LOGIN`, `LIBRUS_PASSWORD` and `LIBRUS_CONTEXT_KEY` in your environment.
83
+ The last variable is the 64-character hex output from that command. Do not use
84
+ your password as this key. Then run:
85
+
86
+ ```python
87
+ import asyncio
88
+ import os
89
+ from datetime import date
90
+
91
+ from librus_python_api import AccountCredentials, HomeworkRangeRequest, LibrusService
92
+
93
+
94
+ async def main() -> None:
95
+ accounts = {
96
+ "school": AccountCredentials(
97
+ login=os.environ["LIBRUS_LOGIN"],
98
+ password=os.environ["LIBRUS_PASSWORD"],
99
+ )
100
+ }
101
+ context_key = bytes.fromhex(os.environ["LIBRUS_CONTEXT_KEY"])
102
+ async with LibrusService(accounts, context_key=context_key) as service:
103
+ client = service.account("school")
104
+ profile = await client.student_information()
105
+ print(profile)
106
+
107
+ today = date.today()
108
+ homework = await client.homework_range(
109
+ HomeworkRangeRequest(today.replace(day=1), today)
110
+ )
111
+ for item in homework.items:
112
+ print(item.subject, item.topic, item.due_on)
113
+
114
+
115
+ asyncio.run(main())
116
+ ```
117
+
118
+ `"school"` is your local account alias, not a student ID. Login occurs on the first
119
+ request. The async context manager closes sessions and outstanding work when it
120
+ exits. Results are immutable dataclasses; personal fields are omitted from their
121
+ `repr`, so access named attributes when displaying data intentionally.
122
+
123
+ ## Common tasks
124
+
125
+ Inside the service context above:
126
+
127
+ ```python
128
+ from datetime import timedelta
129
+
130
+ # School-provided final grades, grouped into typed subject records.
131
+ grades = await client.final_grades()
132
+ for subject in grades.items:
133
+ print(subject.subject, subject.annual.raw)
134
+
135
+ # A week always starts on Monday.
136
+ today = date.today()
137
+ monday = today - timedelta(days=today.weekday())
138
+ timetable = await client.timetable(monday)
139
+
140
+ # Inclusive date window. Neither attendance nor grades computes a GPA.
141
+ attendance = await client.attendance_window(today.replace(day=1), today)
142
+
143
+ # Permit reuse of this account's cached result for up to 60 seconds.
144
+ announcements = await client.announcements(max_age_seconds=60)
145
+ ```
146
+
147
+ Collections expose named record tuples, for example `homework.items`, rather than
148
+ name-keyed dictionaries. Dates are Python `date` values where established by the
149
+ upstream contract. Displayed detail values remain strings; missing or unknown
150
+ values are not replaced with guessed zeros. Full signatures, result fields and
151
+ examples are in the [API reference][api].
152
+
153
+ ### Multiple accounts
154
+
155
+ Add more aliases to `accounts`, then use `service.account(alias)` for each login.
156
+ Reuse **one service** so concurrent calls share its request budget and connection
157
+ limits. A parent login and a student login are separate contexts even when they
158
+ refer to the same student. Separate processes need application-level coordination
159
+ if they share an upstream traffic allowance.
160
+
161
+ ### Bounded pagination and errors
162
+
163
+ ```python
164
+ from librus_python_api import RequestBudget
165
+ from librus_python_api.exceptions import LibrusError, ViewDisabledError
166
+
167
+ budget = RequestBudget(max_requests=20, timeout_seconds=60)
168
+ try:
169
+ batch = await client.messages(limit=25, max_pages=2, budget=budget)
170
+ for message in batch.items:
171
+ print(message.subject)
172
+ # Request another batch with cursor=batch.next_cursor when it is not None.
173
+ except ViewDisabledError:
174
+ print("This school has disabled the requested view.")
175
+ except LibrusError as error:
176
+ print(f"Request failed: {error.kind.value}")
177
+ ```
178
+
179
+ A budget covers login, queueing and all pages of an operation. Defaults allow
180
+ 5 requests/second, a burst of 10 and two simultaneous requests across the service.
181
+ Fresh reads are the default. Unsupported layouts raise typed errors rather than
182
+ silently returning incomplete data. Cursors detect changes; they are not snapshots.
183
+
184
+ ## Messages, files and notifications
185
+
186
+ - **Message content:** opening received content can mark it read. Pass
187
+ `allow_mark_read=True` only when your application permits that effect.
188
+ - **Sending:** prepare a single-use send attempt and obtain approval in your
189
+ application. An `UNKNOWN` result must not trigger an automatic resend.
190
+ - **Attachments:** stream bytes with explicit limits, or use the optional
191
+ `files.publish_attachment()` helper to save atomically into an existing directory.
192
+ - **Notifications:** optional `NotificationStore` and `NotificationWorkflow`
193
+ provide durable checkpoints, pending delivery and explicit acknowledgement.
194
+ - **Persistent sends:** optional `PersistenceStore` records confirmations, claims
195
+ and uncertain outcomes across restarts. These stores require private POSIX
196
+ directories. Core reads do not create files.
197
+
198
+ The legacy and modern messaging backends have distinct references and permissions;
199
+ select one explicitly. See the [API reference][api] for complete workflows.
200
+
201
+ ## Context keys and upgrading from 0.6
202
+
203
+ `LibrusService` now requires `context_key`, exactly 32 secret random bytes.
204
+ `client.context.identifier` is an HMAC-SHA256 pseudonym bound to that key, the
205
+ alias, login and configured origins. Password changes preserve it. Different
206
+ application keys produce different identifiers; the identifier is not a login
207
+ credential or permission token. The separate `context.alias` is still plain text.
208
+
209
+ **Back up and reuse the key with persistent state.** Losing or rotating it changes
210
+ all context identifiers. Do not treat an empty history under a different key as
211
+ permission to resend a message. Version 0.7 uses storage and notification archive
212
+ format 3 and refuses older formats without modifying them. Keep 0.6 stores and
213
+ their pending/UNKNOWN records for reconciliation; there is no automatic migration.
214
+ See the [upgrade guide][upgrade] before reusing a persistent application.
215
+
216
+ Loguru is no longer a dependency. For optional diagnostics, pass
217
+ `diagnostic_sink=librus_python_api.diagnostics.logging_sink` after importing that
218
+ function. Configure handlers with Python's standard `logging` module. You can
219
+ also pass your own callable; events contain allowlisted timing/outcome fields,
220
+ not credentials, account aliases or response bodies. The old `loguru_sink` was
221
+ removed in 0.7.
222
+
223
+ ## Supported features and limitations
224
+
225
+ | Area | Available |
226
+ | --- | --- |
227
+ | School data | Profile, grades, attendance, timetable, announcements, agenda, homework and completed lessons |
228
+ | Communication | Legacy/modern message lists and content, recipient discovery, bounded attachment streams and explicit sending |
229
+ | Application workflows | Shared multi-account limits, caching, notification checkpoints, optional durable send/notification stores |
230
+
231
+ Completed lessons may be disabled by the school. Behaviour notes and observation
232
+ cards are not implemented. Some recipient, archive and receipt layouts remain
233
+ unqualified; backend acceptance is not proof of delivery. Tests cover supported
234
+ contracts, not every school or role. Detailed coverage is in the
235
+ [verification log][verification] and [roadmap][roadmap].
236
+
237
+ ## Development and support
238
+
239
+ Use a repository checkout for tests and development tools; they are deliberately
240
+ excluded from published source archives. See [CONTRIBUTING.md][contributing] for
241
+ setup and offline checks. Report bugs through [GitHub Issues][issues] and security
242
+ concerns according to [SECURITY.md][security]. Do not attach credentials or raw
243
+ school data to public reports.
244
+
245
+ MIT licensed. See [LICENSE][license].
246
+
247
+ [api]: https://github.com/krzysztofbury/librus-python-api/blob/main/API.md
248
+ [upgrade]: https://github.com/krzysztofbury/librus-python-api/blob/main/contracts/account-context.md
249
+ [verification]: https://github.com/krzysztofbury/librus-python-api/blob/main/VERIFICATION.md
250
+ [roadmap]: https://github.com/krzysztofbury/librus-python-api/blob/main/TODO.md
251
+ [contributing]: https://github.com/krzysztofbury/librus-python-api/blob/main/CONTRIBUTING.md
252
+ [issues]: https://github.com/krzysztofbury/librus-python-api/issues
253
+ [security]: https://github.com/krzysztofbury/librus-python-api/blob/main/SECURITY.md
254
+ [license]: https://github.com/krzysztofbury/librus-python-api/blob/main/LICENSE
@@ -0,0 +1,46 @@
1
+ librus_python_api/__init__.py,sha256=8cSz477OMuJ_jRqnbMkicSeBpIqC6_nveS9opE0uZHU,5660
2
+ librus_python_api/_notification_codec.py,sha256=st4I6wCyKg2FtRi26dlFO2KiJKe2MGbxC4M4Yso7by4,10064
3
+ librus_python_api/_storage.py,sha256=-TPxHmGT-36Z8XsL7Lx-uEDjBunwR6-DyzPeMjie5Ec,14054
4
+ librus_python_api/announcements.py,sha256=13FoOR1sq7tt9w6aLf0vclcFKDqLUiIYaO5iYD2QVLA,5928
5
+ librus_python_api/attachment_routes.py,sha256=uSg5wCQqHJiphF0KdI0GatQxZr5SLUx6xWQRsl_U4_U,4123
6
+ librus_python_api/attachments.py,sha256=kyIBpFxeK34Al5_jCkxYKpZnsSLa5DYaGS6vHADpy_g,10921
7
+ librus_python_api/attendance.py,sha256=HeFAfnIUn4N__yp1zr635Dve55S2jmwtkeIy_6mV5B4,6924
8
+ librus_python_api/attendance_frequency.py,sha256=VAXZksSbE_o3fhP9vnN3_y0CCFQATFf6EOYhLQn-Fk8,3426
9
+ librus_python_api/budget.py,sha256=8fvqT7eIxyVX2P2OBG27XsYHk_HbtSS4lzuecYpHtgs,2878
10
+ librus_python_api/checkpoint.py,sha256=QBmG9GtpyaGRaFq-FXuGA3yR1BjuMFN9V3RTvvi_FNQ,1930
11
+ librus_python_api/completed_lessons.py,sha256=sR2oLSGb2q1580l-2upCBv9sQfIcHi7CQfWr1K0EFXI,7635
12
+ librus_python_api/config.py,sha256=uXadjaj_CONEt3RJkk6a8ErZAohGVoZRsR9lfMaUtrU,48422
13
+ librus_python_api/detail_fields.py,sha256=K10Stx_ehDTttkUI7-G7a2Ko9exYEu5NJpR-3sBnQ60,1789
14
+ librus_python_api/diagnostics.py,sha256=2l7wq6RA3it-Gwj-JRLcw5qbI37fl3xII6GZA2mZj0o,856
15
+ librus_python_api/exceptions.py,sha256=yWWoo0xyrQAk1d13HOJZnQBsq4ulh_P0hRSI6ZiNQOE,4622
16
+ librus_python_api/files.py,sha256=3lpqePzOuZ4zQ9Ns2yg97OJSiQCXtYk-YVrkttT6aGo,5779
17
+ librus_python_api/grade_parsers.py,sha256=91Kr1-TArzqO-b94ipeIR0a98OV7tRCZQdBWsd9QLZ0,6141
18
+ librus_python_api/grade_records.py,sha256=HvGHQ8Fyx_1Ne5k6N8cBeTqrDCzM5P6saj8eijDbRA4,16793
19
+ librus_python_api/homework_range.py,sha256=erYfvS6PhHU5XhnR6LkiLNy677hGVflY3UAuHOs-YUw,1663
20
+ librus_python_api/lifecycle.py,sha256=XzA5CEdZFo7iL5IMUrawnRvf2m9BFe0OseP-fpQFaAE,856
21
+ librus_python_api/markup.py,sha256=GZk7MmBBGB5SIVY_VngmQxQErI3eGHOpJs3mFVlMR3M,5298
22
+ librus_python_api/message_content.py,sha256=sUPuqpjuavqDyn6QGM5aPH4mpoQV62bDLepSSQ2AsUE,8686
23
+ librus_python_api/messages.py,sha256=K9F8XVs1SojfFpdxxLV8gx9E0g81YxEHjigDzLHmDCg,10230
24
+ librus_python_api/models.py,sha256=DGbxwDymPjJsqRMtqIe2W9KCBXzNiVPhzeXVjPSC9iQ,33106
25
+ librus_python_api/modern_body.py,sha256=3L4P_cIrHdIxZqiinFSQrDMhG6iklT1GoA-VEWMJUiI,2682
26
+ librus_python_api/modern_mailbox.py,sha256=nRrmIHb7RfV49xjUTBlDcgtxOdmXEpp4F1L0IvcGQxw,16118
27
+ librus_python_api/modern_messages.py,sha256=9gysqjUkQGtMKmnCL7boRzNDnAhtL5ibAJ2rkNZWCec,9694
28
+ librus_python_api/notification_models.py,sha256=0n9-k6eZwhRtdjZ6BhxfRmhJilJt77AGVJxMjKl7las,1699
29
+ librus_python_api/notification_persistence.py,sha256=ahvpZF6rRBTLgureC5yhmtjvmlcrF_BNnkyL08CWlj0,41891
30
+ librus_python_api/notification_workflow.py,sha256=ezzquY8bp2Za8pyli_VO1ajG8VLVKn9cCWtDbnLhY8s,11802
31
+ librus_python_api/notifications.py,sha256=VbA8pqNG_19yYo9y7IqpGRmfEb4qDcvA5gBb1syGJKs,7806
32
+ librus_python_api/parsers.py,sha256=OFztbUMDzCywq17BgfFT485bVjyOqLksFHQJthZCx5Q,8120
33
+ librus_python_api/parsing.py,sha256=H71srw5yx-RFrAKZFgj-QFX-DIXZmhnGrDTmJhazCB8,1838
34
+ librus_python_api/persistence.py,sha256=s5KvlwcwJGpsCiaP9lqYlzzyxOrV-MRsrng0t5q13-Y,15086
35
+ librus_python_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ librus_python_api/recipients.py,sha256=6GQCKo-Zjg3VFSg1J8YXcDsy_U3r6-MUS4VCvz0xdaQ,10033
37
+ librus_python_api/scheduler.py,sha256=3JplYxqvQdFz3aKXcoQUJHE9s3FPw_xRhQ2CPNLuQmA,10729
38
+ librus_python_api/school_reads.py,sha256=PCnZ4lv44j-68FgJpQiMteaPSyfhbX8_3CUyE6zhFcQ,14760
39
+ librus_python_api/sending.py,sha256=Nvvk2r-6D6Us53a2fH5GW9PcA6bWEfuOvy6c3M5QVcE,4549
40
+ librus_python_api/service.py,sha256=a8EYBZBihVFe8moY_5SYjCtQSbcDrPwLA7vtcIopsqk,84209
41
+ librus_python_api/timetable.py,sha256=tX1QK9fQABxzfLaHHqTe8e6rAgdOkCoTOfAHdPwNmLE,9836
42
+ librus_python_api/transport.py,sha256=6S5wwBiRkUfKmi6w2t8msxlTSzCo-IbIBE13FeBc7Jw,35357
43
+ librus_python_api-1.0.0rc1.dist-info/METADATA,sha256=5D7YX99pO0euuvydGuWmnDj1kKjX-a8bOmK4roRTwQI,10947
44
+ librus_python_api-1.0.0rc1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
45
+ librus_python_api-1.0.0rc1.dist-info/licenses/LICENSE,sha256=IuerLb3cnZCsaB23znydjjvKqP-LmSJd3syKa9NGUts,1071
46
+ librus_python_api-1.0.0rc1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Krzysztof Bury
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.