edupsyadmin 8.2.0__tar.gz → 8.2.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 (154) hide show
  1. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/CHANGELOG.md +6 -0
  2. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/PKG-INFO +1 -1
  3. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/pyproject.toml +1 -1
  4. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/__version__.py +1 -1
  5. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/add_convenience_data.py +21 -19
  6. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/cft_report.py +5 -3
  7. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/display_client_details.py +4 -4
  8. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/fill_form.py +9 -8
  9. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/lgvt.py +10 -4
  10. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/managers.py +10 -5
  11. edupsyadmin-8.2.1/src/edupsyadmin/api/types.py +81 -0
  12. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/db/clients.py +1 -1
  13. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/clients_overview.py +0 -2
  14. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/dialogs.py +0 -2
  15. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/edit_client.py +12 -13
  16. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/edupsyadmintui.py +5 -6
  17. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/fill_form_app.py +0 -2
  18. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/fill_form_widget.py +5 -6
  19. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/api/test_add_convenience_data.py +8 -4
  20. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/api/test_fill_form.py +13 -8
  21. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/api/test_flatten_pdf.py +6 -2
  22. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/test_edupsyadmintui.py +5 -1
  23. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/.github/workflows/publish.yml +0 -0
  24. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/.gitignore +0 -0
  25. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/.pre-commit-config.yaml +0 -0
  26. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/.python-version +0 -0
  27. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/.readthedocs.yaml +0 -0
  28. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/LICENSE.txt +0 -0
  29. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/README.md +0 -0
  30. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/Makefile +0 -0
  31. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/_static/samplewebuntisfile.csv +0 -0
  32. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/clients_model.rst +0 -0
  33. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/conf.py +0 -0
  34. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/faq.rst +0 -0
  35. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/index.rst +0 -0
  36. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/make.bat +0 -0
  37. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/requirements.txt +0 -0
  38. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/taetigkeitsbericht_keywords.rst +0 -0
  39. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/cli.rst +0 -0
  40. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/configuration.rst +0 -0
  41. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/creatingdocumentation.rst +0 -0
  42. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/editingclients.rst +0 -0
  43. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/encryption.rst +0 -0
  44. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/helpfunction.rst +0 -0
  45. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/installation.rst +0 -0
  46. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/taetigkeitsbericht.rst +0 -0
  47. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/tui.rst +0 -0
  48. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/docs/tutorial/update.rst +0 -0
  49. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/py.typed +0 -0
  50. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/shellscripts/add_iLst.sh +0 -0
  51. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/shellscripts/add_iRst.sh +0 -0
  52. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/shellscripts/add_lrst.sh +0 -0
  53. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/shellscripts/add_nona_nons.sh +0 -0
  54. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/shellscripts/change_keywordtaet_nsessions.sh +0 -0
  55. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/shellscripts/profile.sh +0 -0
  56. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/shellscripts/publish_to_testpypi.sh +0 -0
  57. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/__init__.py +0 -0
  58. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/__main__.py +0 -0
  59. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/alembic/README +0 -0
  60. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/alembic/env.py +0 -0
  61. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/alembic/script.py.mako +0 -0
  62. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/alembic/versions/2026_02_21_0453-4087c43f0c7c_initial_migration.py +0 -0
  63. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/alembic/versions/2026_03_02_1222-635a9d68fbe8_add_case_active_column_to_clients.py +0 -0
  64. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/alembic.ini +0 -0
  65. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/__init__.py +0 -0
  66. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/flatten_pdf.py +0 -0
  67. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/migration.py +0 -0
  68. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/reports.py +0 -0
  69. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/setup_demo.py +0 -0
  70. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/api/taetigkeitsbericht_from_db.py +0 -0
  71. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/__init__.py +0 -0
  72. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/__main__.py +0 -0
  73. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/create_documentation.py +0 -0
  74. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/delete_client.py +0 -0
  75. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/edit_config.py +0 -0
  76. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/flatten_pdfs.py +0 -0
  77. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/get_clients.py +0 -0
  78. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/info.py +0 -0
  79. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/migrate_encryption.py +0 -0
  80. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/mk_report.py +0 -0
  81. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/new_client.py +0 -0
  82. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/rotate_key.py +0 -0
  83. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/set_client.py +0 -0
  84. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/setup_demo.py +0 -0
  85. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/taetigkeitsbericht.py +0 -0
  86. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/commands/tui.py +0 -0
  87. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/cli/utils.py +0 -0
  88. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/core/__init__.py +0 -0
  89. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/core/config.py +0 -0
  90. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/core/encrypt.py +0 -0
  91. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/core/logger.py +0 -0
  92. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/core/paths.py +0 -0
  93. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/data/Faecher_BfsEV.md +0 -0
  94. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/data/Faecher_BfsKi.md +0 -0
  95. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/data/Faecher_Faks.md +0 -0
  96. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/data/sampleconfig.yml +0 -0
  97. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/data/taetigkeitsbericht_categories.csv +0 -0
  98. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/db/__init__.py +0 -0
  99. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/info.py +0 -0
  100. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/__init__.py +0 -0
  101. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/clients_overview_app.py +0 -0
  102. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/edit_client_app.py +0 -0
  103. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/editconfig.py +0 -0
  104. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/editconfig.tcss +0 -0
  105. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/tui/edupsyadmintui.tcss +0 -0
  106. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/utils/__init__.py +0 -0
  107. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/utils/academic_year.py +0 -0
  108. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/utils/convert_measures.py +0 -0
  109. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/utils/datediff.py +0 -0
  110. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/utils/int_from_str.py +0 -0
  111. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/utils/path_utils.py +0 -0
  112. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/utils/python_type.py +0 -0
  113. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/src/edupsyadmin/utils/taetigkeitsbericht_check_key.py +0 -0
  114. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/.gitignore +0 -0
  115. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/api/test_decryption_behavior.py +0 -0
  116. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/api/test_managers.py +0 -0
  117. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/api/test_migration.py +0 -0
  118. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/api/test_taetigkeitsbericht_from_db.py +0 -0
  119. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/benchmark/test_cli_benchmark.py +0 -0
  120. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/benchmark/test_encrypt_benchmark.py +0 -0
  121. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/benchmark/test_key_derivation_benchmark.py +0 -0
  122. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/cli/test_utils.py +0 -0
  123. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/conftest.py +0 -0
  124. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/core/test_config.py +0 -0
  125. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/core/test_encrypt.py +0 -0
  126. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/core/test_logger.py +0 -0
  127. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/data/sample_form_anschreiben.pdf +0 -0
  128. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/data/sample_form_mantelbogen.pdf +0 -0
  129. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/sample_pdf_form.py +0 -0
  130. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/sample_webuntis_export.py +0 -0
  131. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/test_cli.py +0 -0
  132. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_clientsoverview/test_clients_overview.svg +0 -0
  133. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_dialogs/test_yes_no_dialog.svg +0 -0
  134. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_editclient/test_initial_layout_existing_client[client_dict_set_by_user0].svg +0 -0
  135. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_editclient/test_initial_layout_existing_client[client_dict_set_by_user1].svg +0 -0
  136. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_editclient/test_initial_layout_new_client.svg +0 -0
  137. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_editconfig/test_add_new_school_container.svg +0 -0
  138. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_editconfig/test_edit_new_school_container.svg +0 -0
  139. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_editconfig/test_initial_layout.svg +0 -0
  140. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_edupsyadmintui/test_edupsyadmintui_initial_layout.svg +0 -0
  141. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/__snapshots__/test_fill_form_tui/test_initial_layout.svg +0 -0
  142. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/test_clientsoverview.py +0 -0
  143. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/test_dialogs.py +0 -0
  144. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/test_editclient.py +0 -0
  145. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/test_editconfig.py +0 -0
  146. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/tui/test_fill_form_tui.py +0 -0
  147. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/utils/test_academic_year.py +0 -0
  148. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/utils/test_convert_measures.py +0 -0
  149. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/utils/test_datediff.py +0 -0
  150. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/utils/test_int_from_str.py +0 -0
  151. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/utils/test_path_utils.py +0 -0
  152. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/utils/test_python_type.py +0 -0
  153. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/test/edupsyadmin/utils/test_taetigkeitsbericht_check_key.py +0 -0
  154. {edupsyadmin-8.2.0 → edupsyadmin-8.2.1}/uv.lock +0 -0
@@ -1,3 +1,9 @@
1
+ ## 8.2.1 (2026-03-08)
2
+
3
+ ### Refactor
4
+
5
+ - add ClientData TypedDict and enhance type hinting
6
+
1
7
  ## 8.2.0 (2026-03-08)
2
8
 
3
9
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edupsyadmin
3
- Version: 8.2.0
3
+ Version: 8.2.1
4
4
  Summary: edupsyadmin provides tools to help school psychologists with their documentation
5
5
  Author: Lukas Liebermann
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "edupsyadmin"
7
- version = "8.2.0"
7
+ version = "8.2.1"
8
8
  authors = [{ name = "Lukas Liebermann" }]
9
9
  description = "edupsyadmin provides tools to help school psychologists with their documentation"
10
10
  readme = "README.md"
@@ -18,4 +18,4 @@ and backwards compatibility is not guaranteed for minor versions.
18
18
 
19
19
  __all__ = ["__version__"]
20
20
 
21
- __version__ = "8.2.0"
21
+ __version__ = "8.2.1"
@@ -1,9 +1,10 @@
1
1
  from datetime import date
2
2
  from importlib.resources import files
3
- from typing import Any
3
+ from typing import Any, cast
4
4
 
5
5
  from dateutil.parser import parse
6
6
 
7
+ from edupsyadmin.api.types import ClientData
7
8
  from edupsyadmin.core.config import config
8
9
  from edupsyadmin.core.logger import logger
9
10
  from edupsyadmin.utils.academic_year import (
@@ -49,7 +50,7 @@ def _date_to_german_string(isodate: date | str | None) -> str:
49
50
  if (isodate is None) or (isodate == ""):
50
51
  return ""
51
52
  try:
52
- return parse(isodate, dayfirst=False).strftime("%d.%m.%Y")
53
+ return parse(str(isodate), dayfirst=False).strftime("%d.%m.%Y")
53
54
  except ValueError:
54
55
  logger.error(f"'{isodate}' could not be parsed as a date")
55
56
  raise
@@ -58,7 +59,7 @@ def _date_to_german_string(isodate: date | str | None) -> str:
58
59
  raise
59
60
 
60
61
 
61
- def _add_client_address(data: dict[str, Any]) -> None:
62
+ def _add_client_address(data: ClientData) -> None:
62
63
  """Add client's name and address to data."""
63
64
  first_name = data.get("first_name_encr", "")
64
65
  last_name = data.get("last_name_encr", "")
@@ -74,7 +75,7 @@ def _add_client_address(data: dict[str, Any]) -> None:
74
75
  logger.debug(f"Couldn't add home address because of missing data: {e}")
75
76
 
76
77
 
77
- def _add_school_psychologist_address(data: dict[str, Any]) -> None:
78
+ def _add_school_psychologist_address(data: ClientData) -> None:
78
79
  """Add school psychologist's address from config to data."""
79
80
  data["schoolpsy_name"] = config.schoolpsy.schoolpsy_name
80
81
  data["schoolpsy_street"] = config.schoolpsy.schoolpsy_street
@@ -85,7 +86,7 @@ def _add_school_psychologist_address(data: dict[str, Any]) -> None:
85
86
  data["schoolpsy_addr_s_wname"] = data["schoolpsy_addr_m_wname"].replace("\n", ", ")
86
87
 
87
88
 
88
- def _add_school_address(data: dict[str, Any]) -> None:
89
+ def _add_school_address(data: ClientData) -> None:
89
90
  """Add school address from config to data."""
90
91
  school_key = data.get("school")
91
92
  if not school_key:
@@ -102,7 +103,7 @@ def _add_school_address(data: dict[str, Any]) -> None:
102
103
  data["school_addr_s_wname"] = data["school_addr_m_wname"].replace("\n", ", ")
103
104
 
104
105
 
105
- def _add_lrst_diagnosis(data: dict[str, Any]) -> None:
106
+ def _add_lrst_diagnosis(data: ClientData) -> None:
106
107
  """Add long version of LRSt diagnosis to data."""
107
108
  diagnosis = data.get("lrst_diagnosis_encr")
108
109
  if not diagnosis:
@@ -122,23 +123,23 @@ def _add_lrst_diagnosis(data: dict[str, Any]) -> None:
122
123
  )
123
124
 
124
125
 
125
- def _add_dates(data: dict[str, Any]) -> None:
126
+ def _add_dates(data: ClientData) -> None:
126
127
  """Add various formatted dates to data."""
127
128
  data["today_date"] = date.today()
128
- dates_to_convert = [
129
- "birthday_encr",
130
- "today_date",
131
- "lrst_last_test_date_encr",
132
- "document_shredding_date",
129
+ dates_to_convert: list[tuple[str, str]] = [
130
+ ("birthday_encr", "birthday_encr_de"),
131
+ ("today_date", "today_date_de"),
132
+ ("lrst_last_test_date_encr", "lrst_last_test_date_encr_de"),
133
+ ("document_shredding_date", "document_shredding_date_de"),
133
134
  ]
134
- for idate in dates_to_convert:
135
- gdate = idate + "_de"
136
- data[gdate] = _date_to_german_string(data.get(idate))
135
+ for idate, gdate in dates_to_convert:
136
+ # TypedDict doesn't support dynamic key access with variables
137
+ cast(Any, data)[gdate] = _date_to_german_string(data.get(cast(Any, idate)))
137
138
 
138
139
  data["school_year"] = get_this_academic_year_string()
139
140
 
140
141
 
141
- def _add_nta_schoolyear(data: dict[str, Any]) -> None:
142
+ def _add_nta_schoolyear(data: ClientData) -> None:
142
143
  """Add NTA/NOS end schoolyear to data."""
143
144
  if (
144
145
  data.get("nta_nos_end")
@@ -147,12 +148,13 @@ def _add_nta_schoolyear(data: dict[str, Any]) -> None:
147
148
  ):
148
149
  data["nta_nos_end_schoolyear"] = get_academic_year_string(
149
150
  get_estimated_end_of_academic_year(
150
- grade_current=data["class_int"], grade_target=data["nta_nos_end_grade"]
151
+ grade_current=cast(int, data["class_int"]),
152
+ grade_target=cast(int, data["nta_nos_end_grade"]),
151
153
  )
152
154
  )
153
155
 
154
156
 
155
- def _convert_lrst_test_by(data: dict[str, Any]) -> None:
157
+ def _convert_lrst_test_by(data: ClientData) -> None:
156
158
  """Convert lrst_last_test_by_encr to numerical value for forms."""
157
159
  test_by = data.get("lrst_last_test_by_encr")
158
160
  if not test_by:
@@ -176,7 +178,7 @@ def _convert_lrst_test_by(data: dict[str, Any]) -> None:
176
178
  )
177
179
 
178
180
 
179
- def add_convenience_data(data: dict[str, Any]) -> dict[str, Any]:
181
+ def add_convenience_data(data: ClientData) -> ClientData:
180
182
  """
181
183
  Füge Daten hinzu, die sich aus einem Eintrag in einer `Client`-Datenbank,
182
184
  der Konfigurationsdatei und einer Datei zu den Schulfächern (optional)
@@ -5,6 +5,7 @@ from edupsyadmin.api.managers import ClientsManager
5
5
 
6
6
  # TODO: Remove FPDF dependency?
7
7
  from edupsyadmin.api.reports import Report, normal_distribution_plot
8
+ from edupsyadmin.api.types import ClientData
8
9
  from edupsyadmin.utils.convert_measures import iq_to_t, iq_to_z
9
10
  from edupsyadmin.utils.datediff import mydatediff
10
11
 
@@ -34,10 +35,11 @@ def create_report(
34
35
  clients_manager = ClientsManager(
35
36
  database_url=database_url,
36
37
  )
37
- client_dict = clients_manager.get_decrypted_client(client_id)
38
+ client_dict: ClientData = clients_manager.get_decrypted_client(client_id)
38
39
 
39
40
  testdate = datetime.strptime(test_date, "%Y-%m-%d").date()
40
- birthday = datetime.strptime(client_dict["birthday_encr"], "%Y-%m-%d").date()
41
+ birthday_str = client_dict.get("birthday_encr", "")
42
+ birthday = datetime.strptime(birthday_str, "%Y-%m-%d").date()
41
43
 
42
44
  age_str = f"Alter: {mydatediff(birthday, testdate)}"
43
45
  text = []
@@ -110,7 +112,7 @@ def create_report(
110
112
  heading = f"CFT 20-R (Testdatum: {testdate}; Code: {client_id})"
111
113
  report = Report(heading, text, fn_plot)
112
114
  report.print_page()
113
- report.output(directory / Path(f"{client_id}_Auswertung.pdf"))
115
+ report.output(str(directory / Path(f"{client_id}_Auswertung.pdf")))
114
116
 
115
117
  # remove the plot png
116
118
  fn_plot.unlink()
@@ -1,16 +1,16 @@
1
- from typing import Any
2
-
3
1
  from rich.console import Console
4
2
  from rich.panel import Panel
5
3
  from rich.table import Table
6
4
 
5
+ from edupsyadmin.api.types import ClientData
6
+
7
7
 
8
- def display_client_details(client_data: dict[str, Any]) -> None:
8
+ def display_client_details(client_data: ClientData) -> None:
9
9
  """Displays client details in a formatted way using rich."""
10
10
  console = Console()
11
11
 
12
12
  # Helper to print a table for a group of fields
13
- def print_group_table(data: dict, title: str, fields: list[str]) -> None:
13
+ def print_group_table(data: ClientData, title: str, fields: list[str]) -> None:
14
14
  table = Table(show_header=False, box=None, padding=(0, 1))
15
15
  table.add_column(style="cyan")
16
16
  table.add_column()
@@ -7,10 +7,11 @@ from typing import Any
7
7
 
8
8
  from liquid import Template, exceptions
9
9
 
10
+ from edupsyadmin.api.types import ClientData
10
11
  from edupsyadmin.core.logger import logger
11
12
 
12
13
 
13
- def _modify_bool_and_none_for_pdf_form(data: dict[str, Any]) -> dict[str, Any]:
14
+ def _modify_bool_and_none_for_pdf_form(data: ClientData) -> dict[str, Any]:
14
15
  """
15
16
  Replace every boolean True with 'Yes' and False with 'Off', which are the
16
17
  values checkboxes accept in most PDF forms. Replace None with empty
@@ -20,7 +21,7 @@ def _modify_bool_and_none_for_pdf_form(data: dict[str, Any]) -> dict[str, Any]:
20
21
  :return: a modified dictionary where booleans are replaced
21
22
  with string values and None values are replaced with an empty string
22
23
  """
23
- updated_data = {}
24
+ updated_data: dict[str, Any] = {}
24
25
  logger.debug("Replacing True with 'Yes' and False with 'Off'")
25
26
  for key, value in data.items():
26
27
  if isinstance(value, bool):
@@ -32,7 +33,7 @@ def _modify_bool_and_none_for_pdf_form(data: dict[str, Any]) -> dict[str, Any]:
32
33
  return updated_data
33
34
 
34
35
 
35
- def write_form_pdf(fn: Path, out_fn: Path, data: dict[str, Any]) -> None:
36
+ def write_form_pdf(fn: Path, out_fn: Path, data: ClientData) -> None:
36
37
  """
37
38
  Fill a pdf form with data using pypdf.
38
39
 
@@ -57,7 +58,7 @@ def write_form_pdf(fn: Path, out_fn: Path, data: dict[str, Any]) -> None:
57
58
  if not fields:
58
59
  logger.debug(f"The file {fn} is not a form.")
59
60
  else:
60
- logger.debug("\nForm fields:\n{fields.keys()}")
61
+ logger.debug(f"\nForm fields:\n{fields.keys()}")
61
62
  logger.debug(f"\nData keys:\n{data_wo_bool.keys()}")
62
63
  comb_key_fields = product(range(len(reader.pages)), fields.keys())
63
64
  for i, key in comb_key_fields:
@@ -77,7 +78,7 @@ def write_form_pdf(fn: Path, out_fn: Path, data: dict[str, Any]) -> None:
77
78
  writer.write(output_stream)
78
79
 
79
80
 
80
- def write_form_pdf2(fn: Path, out_fn: Path, data: dict[str, Any]) -> None:
81
+ def write_form_pdf2(fn: Path, out_fn: Path, data: ClientData) -> None:
81
82
  """
82
83
  Fill a pdf form with data using fillpdf.
83
84
 
@@ -101,7 +102,7 @@ def write_form_pdf2(fn: Path, out_fn: Path, data: dict[str, Any]) -> None:
101
102
  shutil.copyfile(fn, out_fn)
102
103
 
103
104
 
104
- def write_form_md(fn: Path, out_fn: Path, data: dict[str, Any]) -> None:
105
+ def write_form_md(fn: Path, out_fn: Path, data: ClientData) -> None:
105
106
  """
106
107
  Render a liquid template with data passed to the function.
107
108
 
@@ -127,7 +128,7 @@ def write_form_md(fn: Path, out_fn: Path, data: dict[str, Any]) -> None:
127
128
 
128
129
 
129
130
  def fill_form(
130
- client_data: dict[str, Any],
131
+ client_data: ClientData,
131
132
  form_paths: Sequence[str | os.PathLike[str]],
132
133
  out_dir: str | os.PathLike[str] = ".",
133
134
  use_fillpdf: bool = True,
@@ -150,7 +151,7 @@ def fill_form(
150
151
  raise FileNotFoundError(
151
152
  f"The template file does not exist: {fp}; cwd is: {Path.cwd()}"
152
153
  )
153
- out_fp = Path(out_dir, f"{client_data['client_id']}_{fp.name}")
154
+ out_fp = Path(out_dir, f"{client_data.get('client_id')}_{fp.name}")
154
155
  logger.info(f"Writing to {out_fp.resolve()}")
155
156
  if fp.suffix == ".md":
156
157
  write_form_md(fp, out_fp, client_data)
@@ -3,10 +3,12 @@ import math
3
3
  import os
4
4
  from datetime import date, datetime
5
5
  from pathlib import Path
6
+ from typing import Any, cast
6
7
 
7
8
  import pandas as pd
8
9
 
9
10
  from edupsyadmin.api.managers import ClientsManager
11
+ from edupsyadmin.api.types import ClientData
10
12
  from edupsyadmin.core.config import config
11
13
  from edupsyadmin.utils.convert_measures import percentile_to_t
12
14
 
@@ -122,10 +124,14 @@ def mk_report(
122
124
  t_day = datetime.strptime(test_date, "%Y-%m-%d").date()
123
125
 
124
126
  clients_manager = ClientsManager(database_url=database_url)
125
- client_dict = clients_manager.get_decrypted_client(client_id)
126
-
127
- name = client_dict["first_name_encr"] + " " + client_dict["last_name_encr"]
128
- schoolyear = client_dict["class_int"]
127
+ client_dict: ClientData = clients_manager.get_decrypted_client(client_id)
128
+
129
+ name = (
130
+ client_dict.get("first_name_encr", "")
131
+ + " "
132
+ + client_dict.get("last_name_encr", "")
133
+ )
134
+ schoolyear = int(cast(Any, client_dict.get("class_int", 0)))
129
135
 
130
136
  results = get_indeces(fn_csv, name, schoolyear, t_day, version)
131
137
 
@@ -1,11 +1,12 @@
1
1
  import logging # just for interaction with the sqlalchemy logger
2
2
  from datetime import datetime
3
- from typing import Any
3
+ from typing import Any, cast
4
4
 
5
5
  import pandas as pd
6
6
  from sqlalchemy import create_engine, inspect, or_, select
7
7
  from sqlalchemy.orm import sessionmaker
8
8
 
9
+ from edupsyadmin.api.types import ClientData
9
10
  from edupsyadmin.core.config import config
10
11
  from edupsyadmin.core.logger import logger
11
12
  from edupsyadmin.db import clients as clients_db
@@ -50,14 +51,15 @@ class ClientsManager:
50
51
  logger.info(f"added client: {new_client}")
51
52
  return new_client.client_id
52
53
 
53
- def get_decrypted_client(self, client_id: int) -> dict[str, Any]:
54
+ def get_decrypted_client(self, client_id: int) -> ClientData:
54
55
  logger.debug(f"trying to access client (client_id = {client_id})")
55
56
  with self.Session() as session:
56
57
  client = session.get(clients_db.Client, client_id)
57
58
  if client is None:
58
59
  raise ClientNotFoundError(client_id)
59
60
  # Create a clean dictionary using the cached mapper
60
- return {c.key: getattr(client, c.key) for c in self._mapper.column_attrs}
61
+ data = {c.key: getattr(client, c.key) for c in self._mapper.column_attrs}
62
+ return cast(ClientData, data)
61
63
 
62
64
  def get_clients_overview(
63
65
  self,
@@ -129,8 +131,11 @@ class ClientsManager:
129
131
  with self.Session() as session:
130
132
  stmt = select(clients_db.Client)
131
133
  clients = session.scalars(stmt).all()
132
- data = [
133
- {c.key: getattr(client, c.key) for c in self._mapper.column_attrs}
134
+ data: list[ClientData] = [
135
+ cast(
136
+ ClientData,
137
+ {c.key: getattr(client, c.key) for c in self._mapper.column_attrs},
138
+ )
134
139
  for client in clients
135
140
  ]
136
141
  return pd.DataFrame(data)
@@ -0,0 +1,81 @@
1
+ from datetime import date, datetime
2
+ from typing import TypedDict
3
+
4
+
5
+ class ClientData(TypedDict, total=False):
6
+ # Base fields from Client model
7
+ client_id: int
8
+ first_name_encr: str
9
+ last_name_encr: str
10
+ gender_encr: str
11
+ birthday_encr: str
12
+ street_encr: str
13
+ city_encr: str
14
+ parent_encr: str
15
+ telephone1_encr: str
16
+ telephone2_encr: str
17
+ email_encr: str
18
+ notes_encr: str
19
+ keyword_taet_encr: str
20
+ lrst_diagnosis_encr: str
21
+ lrst_last_test_date_encr: str
22
+ lrst_last_test_by_encr: str
23
+ school: str
24
+ entry_date: date | None
25
+ class_name: str | None
26
+ class_int: int | None
27
+ estimated_graduation_date: date | None
28
+ document_shredding_date: date | None
29
+ datetime_created: datetime
30
+ datetime_lastmodified: datetime
31
+ notenschutz: bool
32
+ nos_rs: bool
33
+ nos_rs_ausn: bool
34
+ nos_rs_ausn_faecher: str | None
35
+ nos_les: bool
36
+ nos_other: bool
37
+ nos_other_details: str | None
38
+ nachteilsausgleich: bool
39
+ nta_zeitv: bool
40
+ nta_zeitv_vieltext: int | None
41
+ nta_zeitv_wenigtext: int | None
42
+ nta_font: bool
43
+ nta_aufg: bool
44
+ nta_struktur: bool
45
+ nta_arbeitsm: bool
46
+ nta_ersgew: bool
47
+ nta_vorlesen: bool
48
+ nta_other: bool
49
+ nta_other_details: str | None
50
+ nta_nos_notes: str | None
51
+ nta_nos_end: bool
52
+ nta_nos_end_grade: int | None
53
+ min_sessions: int
54
+ n_sessions: int
55
+ case_active: bool
56
+
57
+ # Convenience fields added by add_convenience_data
58
+ name: str
59
+ addr_s_nname: str
60
+ addr_m_wname: str
61
+ schoolpsy_name: str
62
+ schoolpsy_street: str
63
+ schoolpsy_city: str
64
+ schoolpsy_addr_m_wname: str
65
+ schoolpsy_addr_s_wname: str
66
+ school_name: str
67
+ school_street: str
68
+ school_city: str
69
+ school_head_w_school: str
70
+ school_addr_m_wname: str
71
+ school_addr_s_wname: str
72
+ lrst_diagnosis_long: str
73
+ today_date: date
74
+ birthday_encr_de: str
75
+ today_date_de: str
76
+ lrst_last_test_date_encr_de: str
77
+ document_shredding_date_de: str
78
+ school_year: str
79
+ nta_nos_end_schoolyear: str
80
+ lrst_schpsy: int
81
+ school_subjects: str
@@ -369,7 +369,7 @@ class Client(Base):
369
369
  ) -> None:
370
370
  client_id_int_or_none = to_int_or_none(client_id)
371
371
  if client_id_int_or_none is not None:
372
- self.client_id = client_id
372
+ self.client_id = client_id_int_or_none
373
373
  self.first_name_encr = first_name_encr
374
374
  self.last_name_encr = last_name_encr
375
375
  self.gender_encr = (
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import ClassVar
4
2
 
5
3
  import pandas as pd
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  from textual.app import ComposeResult
4
2
  from textual.containers import Grid
5
3
  from textual.screen import ModalScreen
@@ -1,5 +1,5 @@
1
- from datetime import date
2
- from typing import Any, ClassVar
1
+ from datetime import date, datetime
2
+ from typing import Any, ClassVar, cast
3
3
 
4
4
  from textual import on
5
5
  from textual.app import ComposeResult
@@ -18,6 +18,7 @@ from textual.widgets import (
18
18
  )
19
19
  from textual.widgets._select import NoSelection
20
20
 
21
+ from edupsyadmin.api.types import ClientData
21
22
  from edupsyadmin.core.config import config
22
23
  from edupsyadmin.db.clients import LRST_DIAG, LRST_TEST_BY, Client
23
24
  from edupsyadmin.utils.python_type import get_python_type
@@ -64,15 +65,13 @@ LRST_FIELDS = [
64
65
  DATE_REGEX = r"\d{4}-[0-1]\d-[0-3]\d"
65
66
 
66
67
 
67
- def _to_str_or_bool(value: None | date | bool | str | int | float) -> str | bool | None:
68
+ def _to_str_or_bool(value: object) -> str | bool | None:
68
69
  if value is None:
69
70
  return None
70
- if isinstance(value, date):
71
- return value.isoformat()
72
- if isinstance(value, bool | str): # check this before checking if int!
71
+ if isinstance(value, bool):
73
72
  return value
74
- if isinstance(value, int | float):
75
- return str(value)
73
+ if isinstance(value, (date, datetime)):
74
+ return value.isoformat()
76
75
  return str(value)
77
76
 
78
77
 
@@ -284,7 +283,7 @@ class EditClient(Container):
284
283
  yield RichLog(classes="log", id="edit-client-log")
285
284
 
286
285
  def _normalize_original_data(
287
- self, data: dict[str, Any]
286
+ self, data: ClientData
288
287
  ) -> dict[str, str | bool | None]:
289
288
  return {k: _to_str_or_bool(v) for k, v in data.items()}
290
289
 
@@ -394,7 +393,7 @@ class EditClient(Container):
394
393
  widget.tooltip = tooltip
395
394
  return widget
396
395
 
397
- def update_client(self, client_id: int | None, data: dict[str, Any] | None) -> None:
396
+ def update_client(self, client_id: int | None, data: ClientData | None) -> None:
398
397
  self.client_id = client_id
399
398
  data = data or _get_empty_client_dict()
400
399
 
@@ -519,12 +518,12 @@ class EditClient(Container):
519
518
 
520
519
 
521
520
  # TODO: make defaults configurable
522
- def _get_empty_client_dict() -> dict[str, str | bool | int]:
521
+ def _get_empty_client_dict() -> ClientData:
523
522
  defaults = {
524
523
  "min_sessions": 45,
525
524
  "n_session": 1,
526
525
  }
527
- empty_client_dict: dict[str, str | bool | int] = {}
526
+ empty_client_dict: dict[str, Any] = {}
528
527
  for db_column in Client.__table__.columns:
529
528
  field_type = get_python_type(db_column.type)
530
529
  name = db_column.name
@@ -535,4 +534,4 @@ def _get_empty_client_dict() -> dict[str, str | bool | int]:
535
534
  empty_client_dict[name] = False
536
535
  else:
537
536
  empty_client_dict[name] = ""
538
- return empty_client_dict
537
+ return cast(ClientData, empty_client_dict)
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import TYPE_CHECKING, Any, ClassVar
4
2
 
5
3
  from textual import work
@@ -10,6 +8,7 @@ from textual.message import Message
10
8
  from textual.widgets import Footer, Header, LoadingIndicator
11
9
 
12
10
  from edupsyadmin.api.fill_form import fill_form
11
+ from edupsyadmin.api.types import ClientData
13
12
  from edupsyadmin.tui.clients_overview import ClientsOverview
14
13
  from edupsyadmin.tui.edit_client import EditClient
15
14
  from edupsyadmin.tui.fill_form_widget import FillForm, FillFormScreen
@@ -206,7 +205,7 @@ class EdupsyadminTui(App[None]):
206
205
  if message.client_id is not None and message.client_data is not None:
207
206
  edit_client_widget.update_client(message.client_id, message.client_data)
208
207
  else:
209
- edit_client_widget.update_client(None, {})
208
+ edit_client_widget.update_client(None, None)
210
209
  self.notify("Daten erfolgreich gespeichert.", severity="information")
211
210
 
212
211
  async def on_edit_client_cancel_edit(self, message: EditClient.CancelEdit) -> None:
@@ -214,7 +213,7 @@ class EdupsyadminTui(App[None]):
214
213
  return
215
214
 
216
215
  edit_client_widget = self.query_one(EditClient)
217
- edit_client_widget.update_client(None, {})
216
+ edit_client_widget.update_client(None, None)
218
217
  self.notify("Bearbeitung abgebrochen.", severity="information")
219
218
 
220
219
  async def on_fill_form_start_fill(self, message: FillForm.StartFill) -> None:
@@ -295,7 +294,7 @@ class EdupsyadminTui(App[None]):
295
294
  def __init__(
296
295
  self,
297
296
  client_id: int,
298
- client_data: dict[str, Any] | None,
297
+ client_data: ClientData | None,
299
298
  error: Exception | None = None,
300
299
  ) -> None:
301
300
  self.client_id = client_id
@@ -307,7 +306,7 @@ class EdupsyadminTui(App[None]):
307
306
  def __init__(
308
307
  self,
309
308
  client_id: int | None = None,
310
- client_data: dict[str, Any] | None = None,
309
+ client_data: ClientData | None = None,
311
310
  error: Exception | None = None,
312
311
  ) -> None:
313
312
  self.client_id = client_id
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import TYPE_CHECKING, ClassVar
4
2
 
5
3
  from textual import work
@@ -1,7 +1,5 @@
1
- from __future__ import annotations
2
-
3
1
  from pathlib import Path
4
- from typing import TYPE_CHECKING, Any
2
+ from typing import TYPE_CHECKING
5
3
 
6
4
  from rich.text import Text
7
5
  from textual import on
@@ -21,6 +19,7 @@ from textual.widgets import (
21
19
  Static,
22
20
  )
23
21
 
22
+ from edupsyadmin.api.types import ClientData
24
23
  from edupsyadmin.core.config import config
25
24
 
26
25
  if TYPE_CHECKING:
@@ -164,7 +163,7 @@ class FillForm(Widget):
164
163
  event.input.value = str(current_path)
165
164
 
166
165
  # TODO: improve confusing function name
167
- def update_client(self, client_id: int, client_data: dict[str, Any]) -> None:
166
+ def update_client(self, client_id: int, client_data: ClientData) -> None:
168
167
  """Update the widget with data for a specific client."""
169
168
  self.client_id = client_id
170
169
  info = self.query_one("#client-info", Static)
@@ -177,7 +176,7 @@ class FillForm(Widget):
177
176
 
178
177
  # TODO: improve confusing function name
179
178
  # TODO: remove redundancy
180
- def update_clients(self, clients_data: dict[int, dict[str, Any]]) -> None:
179
+ def update_clients(self, clients_data: dict[int, ClientData]) -> None:
181
180
  """Update the widget with data for multiple clients."""
182
181
  self.client_ids = list(clients_data.keys())
183
182
  info = self.query_one("#client-info", Static)
@@ -257,7 +256,7 @@ class FillFormScreen(Screen):
257
256
 
258
257
  def on_mount(self) -> None:
259
258
  """Load the client data and update the FillForm widget."""
260
- clients_data = {}
259
+ clients_data: dict[int, ClientData] = {}
261
260
  for client_id in self.client_ids:
262
261
  clients_data[client_id] = self.clients_manager.get_decrypted_client(
263
262
  client_id