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