librus-python-api 1.6.0__tar.gz → 1.6.1__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 (56) hide show
  1. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/CHANGELOG.md +10 -0
  2. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/PKG-INFO +6 -6
  3. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/README.md +5 -5
  4. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/__init__.py +1 -1
  5. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/config.py +4 -0
  6. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/markup.py +4 -2
  7. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/school_reads.py +18 -3
  8. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/.gitignore +0 -0
  9. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/API.md +0 -0
  10. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/LICENSE +0 -0
  11. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/SECURITY.md +0 -0
  12. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/pyproject.toml +0 -0
  13. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/_notification_bootstrap.py +0 -0
  14. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/_notification_codec.py +0 -0
  15. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/_storage.py +0 -0
  16. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/_windows_filesystem.py +0 -0
  17. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/announcements.py +0 -0
  18. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/attachment_routes.py +0 -0
  19. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/attachments.py +0 -0
  20. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/attendance.py +0 -0
  21. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/attendance_frequency.py +0 -0
  22. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/budget.py +0 -0
  23. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/checkpoint.py +0 -0
  24. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/class_free_days.py +0 -0
  25. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/completed_lessons.py +0 -0
  26. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/detail_fields.py +0 -0
  27. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/diagnostics.py +0 -0
  28. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/exceptions.py +0 -0
  29. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/files.py +0 -0
  30. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/formative_grades.py +0 -0
  31. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/grade_parsers.py +0 -0
  32. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/grade_records.py +0 -0
  33. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/homework_range.py +0 -0
  34. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/lifecycle.py +0 -0
  35. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/message_content.py +0 -0
  36. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/messages.py +0 -0
  37. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/models.py +0 -0
  38. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/modern_body.py +0 -0
  39. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/modern_mailbox.py +0 -0
  40. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/modern_messages.py +0 -0
  41. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/notification_models.py +0 -0
  42. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/notification_persistence.py +0 -0
  43. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/notification_workflow.py +0 -0
  44. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/notifications.py +0 -0
  45. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/parsers.py +0 -0
  46. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/parsing.py +0 -0
  47. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/persistence.py +0 -0
  48. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/py.typed +0 -0
  49. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/recipients.py +0 -0
  50. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/scheduler.py +0 -0
  51. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/school_year_archive.py +0 -0
  52. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/sending.py +0 -0
  53. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/service.py +0 -0
  54. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/timetable.py +0 -0
  55. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/src/librus_python_api/transport.py +0 -0
  56. {librus_python_api-1.6.0 → librus_python_api-1.6.1}/uv.lock +0 -0
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.1 (2026-10-12) - Sunday grade dates and calendar closures
4
+
5
+ - Accept both observed Sunday tooltip labels, `nd.` and `ndz.`, while retaining
6
+ the original grade metadata and strict rejection of unknown date suffixes.
7
+ - Preserve calendar closure events whose handler targets the separate closure
8
+ details page. Such events retain their text and date with `reference=None`;
9
+ they are never misrouted to ordinary agenda details or fetched automatically.
10
+ - Add regressions based on guarded structure-only observations with independently
11
+ authored text and IDs. Unknown closure handlers still fail closed.
12
+
3
13
  ## 1.6.0 (2026-10-09) - Class free days and capability outcomes
4
14
 
5
15
  Compatible minor release with a new typed module-unavailable outcome. Consumers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: librus-python-api
3
- Version: 1.6.0
3
+ Version: 1.6.1
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.6.0` adds class free days and a typed module-unavailable outcome,
46
- plus an independent live-check freshness watchdog and targeted mutation/load
47
- evidence. See [verification](VERIFICATION.md).
45
+ **Status:** `1.6.1` fixes Sunday grade-date labels and calendar closure events.
46
+ Class free days, typed capability outcomes and the live-check freshness watchdog
47
+ remain available. 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.6.0
57
+ python -m pip install librus-python-api==1.6.1
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.6.0-py3-none-any.whl
73
+ python -m pip install ./dist/librus_python_api-1.6.1-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.6.0` adds class free days and a typed module-unavailable outcome,
20
- plus an independent live-check freshness watchdog and targeted mutation/load
21
- evidence. See [verification](VERIFICATION.md).
19
+ **Status:** `1.6.1` fixes Sunday grade-date labels and calendar closure events.
20
+ Class free days, typed capability outcomes and the live-check freshness watchdog
21
+ remain available. 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.6.0
31
+ python -m pip install librus-python-api==1.6.1
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.6.0-py3-none-any.whl
47
+ python -m pip install ./dist/librus_python_api-1.6.1-py3-none-any.whl
48
48
  ```
49
49
 
50
50
  No CLI or background process is installed: import the library in your own program.
@@ -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.0"
141
+ __version__ = "1.6.1"
142
142
 
143
143
  __all__ = [
144
144
  "ArchiveAbsences",
@@ -860,6 +860,8 @@ HOMEWORK_MARK_DONE_HANDLER = (
860
860
  r"\s*showConfirmQuestion\(\s*[0-9]{1,64}\s*,\s*[0-9]{1,64}\s*\);?\s*"
861
861
  )
862
862
  WEEKDAY_LABELS = ("pon.", "wt.", "śr.", "czw.", "pt.", "sob.", "ndz.")
863
+ # Grade tooltips also use the short Sunday display label.
864
+ CIVIL_DATE_WEEKDAY_LABELS = frozenset((*WEEKDAY_LABELS, "nd."))
863
865
  # Observed page-level notices shown instead of the requested content.
864
866
  PAGE_NOTICES = MappingProxyType(
865
867
  {
@@ -881,6 +883,8 @@ COMPLETED_LESSONS_MAX_PAGE_ITEMS = 256
881
883
  COMPLETED_LESSONS_MAX_BATCH_PAGES = 8
882
884
  COMPLETED_LESSONS_MAX_BATCH_ITEMS = 256
883
885
  AGENDA_DETAIL_PATH_PREFIX = "/terminarz/szczegoly/"
886
+ # Recognition only: closure details are not ordinary agenda-detail references.
887
+ AGENDA_CLOSURE_DETAIL_PATH_PREFIX = "/terminarz/szczegoly_wolne/"
884
888
  HOMEWORK_DETAIL_PATH_PREFIX = "/moje_zadania/podglad/"
885
889
  MESSAGE_MAX_PAGE_COUNT = 1000
886
890
  MESSAGE_MAX_PAGE_ITEMS = 50
@@ -9,11 +9,11 @@ from lxml import etree, html
9
9
 
10
10
  from librus_python_api.config import (
11
11
  ATTENDANCE_DETAIL_PATH_PREFIX,
12
+ CIVIL_DATE_WEEKDAY_LABELS,
12
13
  GRADE_MAX_COLUMNS,
13
14
  GRADE_MAX_METADATA_FIELDS,
14
15
  GRADE_MAX_METADATA_LENGTH,
15
16
  UPSTREAM_ORIGINS,
16
- WEEKDAY_LABELS,
17
17
  )
18
18
  from librus_python_api.exceptions import ErrorKind, LibrusError
19
19
  from librus_python_api.parsers import parse_html_document
@@ -109,7 +109,9 @@ def tooltip_fields(element: html.HtmlElement) -> dict[str, str]:
109
109
  def civil_date(value: str | None) -> date:
110
110
  """An ISO date, optionally followed by its weekday label in parentheses."""
111
111
  match = re.fullmatch(r"([0-9]{4}-[0-9]{2}-[0-9]{2})(?: \(([^()]+)\))?", value or "")
112
- if match is None or (match[2] is not None and match[2] not in WEEKDAY_LABELS):
112
+ if match is None or (
113
+ match[2] is not None and match[2] not in CIVIL_DATE_WEEKDAY_LABELS
114
+ ):
113
115
  raise LibrusError(ErrorKind.PARSE)
114
116
  try:
115
117
  return date.fromisoformat(match[1])
@@ -9,6 +9,7 @@ from lxml import html
9
9
 
10
10
  from librus_python_api import markup
11
11
  from librus_python_api.config import (
12
+ AGENDA_CLOSURE_DETAIL_PATH_PREFIX,
12
13
  AGENDA_DESCRIPTION_LABEL,
13
14
  AGENDA_DETAIL_PATH_PREFIX,
14
15
  AGENDA_TOOLTIP_LABELS,
@@ -42,13 +43,14 @@ def school_reference(
42
43
  ) -> SchoolReference | None:
43
44
  """Find the single detail link.
44
45
 
45
- Agenda cells must not carry any other handler. Homework rows also carry a
46
- mark-done button; the library never follows handlers, so it is inert here.
46
+ Closure handlers stay inert without an ordinary agenda-detail reference.
47
+ Homework rows also carry a mark-done button; handlers are never followed.
47
48
  """
48
49
  prefix = (
49
50
  AGENDA_DETAIL_PATH_PREFIX if kind == "agenda" else HOMEWORK_DETAIL_PATH_PREFIX
50
51
  )
51
52
  found: set[str] = set()
53
+ closure = False
52
54
  for node in nodes:
53
55
  if not isinstance(node.tag, str):
54
56
  continue
@@ -57,6 +59,19 @@ def school_reference(
57
59
  raise LibrusError(ErrorKind.LIMIT)
58
60
  if not action:
59
61
  continue
62
+ if (
63
+ kind == "agenda"
64
+ and node is nodes[0]
65
+ and node.tag == "td"
66
+ and re.fullmatch(
67
+ r"location\.href='"
68
+ + re.escape(AGENDA_CLOSURE_DETAIL_PATH_PREFIX)
69
+ + r"[0-9]{1,64}'",
70
+ action,
71
+ )
72
+ ):
73
+ closure = True
74
+ continue
60
75
  if kind == "homework" and prefix not in action:
61
76
  # Only the observed mark-done button is inert; anything else is new.
62
77
  if re.fullmatch(HOMEWORK_MARK_DONE_HANDLER, action):
@@ -66,7 +81,7 @@ def school_reference(
66
81
  if len(paths) != 1:
67
82
  raise LibrusError(ErrorKind.UNSUPPORTED_CAPABILITY)
68
83
  found.add(paths[0].removeprefix(prefix))
69
- if len(found) > 1:
84
+ if len(found) > 1 or (closure and found):
70
85
  raise LibrusError(ErrorKind.PARSE)
71
86
  return SchoolReference(kind, next(iter(found)), account) if found else None
72
87