gramps-webapi 3.22.2__tar.gz → 3.23.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.github/workflows/test.yml +26 -5
  2. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/PKG-INFO +3 -4
  3. gramps_webapi-3.23.0/alembic_users/versions/7b2e9f4c1a63_allow_several_access_tokens_per_scope.py +81 -0
  4. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/_version.py +1 -1
  5. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/__init__.py +41 -3
  6. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/export.py +63 -5
  7. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/llm/__init__.py +25 -10
  8. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/llm/agent.py +39 -9
  9. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/report.py +113 -35
  10. gramps_webapi-3.23.0/gramps_webapi/api/resources/access_tokens.py +247 -0
  11. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/anniversaries.py +2 -6
  12. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/base.py +0 -18
  13. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/delete.py +3 -5
  14. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/exporters.py +6 -4
  15. gramps_webapi-3.23.0/gramps_webapi/api/resources/filter_param_types.py +202 -0
  16. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/filters.py +98 -2
  17. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/importers.py +12 -3
  18. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/merge.py +9 -6
  19. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/metadata.py +83 -61
  20. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/object_query.py +68 -35
  21. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/oidc.py +2 -0
  22. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/places.py +33 -2
  23. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/reports.py +13 -2
  24. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/schemas.py +229 -14
  25. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/token.py +65 -15
  26. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/user.py +66 -32
  27. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/util.py +186 -25
  28. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/search/__init__.py +26 -4
  29. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/search/indexer.py +114 -56
  30. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/tasks.py +48 -23
  31. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/util.py +55 -19
  32. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/auth/__init__.py +183 -13
  33. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/auth/const.py +17 -0
  34. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/auth/oidc.py +40 -0
  35. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/data/empty_gramps_auth.cfg +0 -1
  36. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi.egg-info/PKG-INFO +3 -4
  37. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi.egg-info/SOURCES.txt +8 -0
  38. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi.egg-info/requires.txt +2 -3
  39. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/pyproject.toml +2 -3
  40. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/__init__.py +6 -0
  41. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_deprecations.py +3 -0
  42. gramps_webapi-3.23.0/tests/test_endpoints/test_access_tokens.py +274 -0
  43. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_exporters.py +74 -0
  44. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_filters.py +324 -0
  45. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_history.py +7 -2
  46. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_importers.py +5 -0
  47. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_metadata.py +31 -0
  48. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_people.py +7 -32
  49. gramps_webapi-3.23.0/tests/test_endpoints/test_place_coordinates.py +165 -0
  50. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_put.py +45 -0
  51. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_reports.py +44 -0
  52. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_restore.py +29 -13
  53. gramps_webapi-3.23.0/tests/test_endpoints/test_token_sync.py +288 -0
  54. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_treeless_admin.py +16 -0
  55. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_user.py +187 -0
  56. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_import_util.py +141 -18
  57. gramps_webapi-3.23.0/tests/test_llm_agent.py +67 -0
  58. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_llm_errors.py +33 -1
  59. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_oidc_auth.py +38 -0
  60. gramps_webapi-3.23.0/tests/test_search_indexer.py +122 -0
  61. gramps_webapi-3.23.0/tests/test_search_reindex.py +83 -0
  62. gramps_webapi-3.23.0/tests/test_stale_semantic_index.py +198 -0
  63. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_util.py +127 -0
  64. gramps_webapi-3.22.2/gramps_webapi/api/resources/access_tokens.py +0 -106
  65. gramps_webapi-3.22.2/tests/test_endpoints/test_access_tokens.py +0 -106
  66. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.devcontainer/Dockerfile +0 -0
  67. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.devcontainer/devcontainer.json +0 -0
  68. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.devcontainer/docker-compose.yml +0 -0
  69. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.dockerignore +0 -0
  70. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.editorconfig +0 -0
  71. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.flake8 +0 -0
  72. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  73. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  74. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  75. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.github/workflows/base-image.yml +0 -0
  76. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.github/workflows/image.yml +0 -0
  77. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.github/workflows/pypi.yml +0 -0
  78. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.gitignore +0 -0
  79. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.pre-commit-config.yaml +0 -0
  80. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.pylintrc +0 -0
  81. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.vscode/extensions.json +0 -0
  82. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.vscode/launch.json +0 -0
  83. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.vscode/settings.json +0 -0
  84. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/.vscode/tasks.json +0 -0
  85. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/CLAUDE.md +0 -0
  86. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/CODE_OF_CONDUCT.md +0 -0
  87. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/CONTRIBUTING.md +0 -0
  88. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/Dockerfile +0 -0
  89. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/Dockerfile.base +0 -0
  90. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/LICENSE +0 -0
  91. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/MANIFEST.in +0 -0
  92. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/README.md +0 -0
  93. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/ROADMAP.md +0 -0
  94. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic.ini +0 -0
  95. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/README +0 -0
  96. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/env.py +0 -0
  97. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/script.py.mako +0 -0
  98. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/2082445b0769_add_oidc_accounts_table_for_secure_.py +0 -0
  99. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/22c8d1fba959_add_trees_enabled.py +0 -0
  100. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/66e56620891a_add_trees_table.py +0 -0
  101. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/6d8f3cb50b71_add_persistent_access_tokens_table.py +0 -0
  102. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/84960b7d968c_use_bigint_for_quota_media.py +0 -0
  103. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/a8e57fe0d82e_add_columns_for_ai_quota.py +0 -0
  104. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/b0582f54029c_use_bigint_for_usage_media.py +0 -0
  105. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/b2c3d4e5f6a7_add_tree_config_column.py +0 -0
  106. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/c1d2e3f4a5b6_merge_task_tree_and_tree_config.py +0 -0
  107. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/c89728e71264_.py +0 -0
  108. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/d4e9a1c7b3f2_drop_unique_constraint_on_user_email.py +0 -0
  109. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/e176543c72a8_add_tree_column_to_user.py +0 -0
  110. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/e5e738d09fa7_added_configuration_table.py +0 -0
  111. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/alembic_users/versions/f3a1c8e92b47_add_task_tree_table.py +0 -0
  112. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/docker-entrypoint.sh +0 -0
  113. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/__init__.py +0 -0
  114. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/__main__.py +0 -0
  115. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/auth.py +0 -0
  116. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/blueprint.py +0 -0
  117. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/cache.py +0 -0
  118. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/check.py +0 -0
  119. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/deprecations.py +0 -0
  120. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/dna.py +0 -0
  121. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/emails.py +0 -0
  122. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/file.py +0 -0
  123. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/html.py +0 -0
  124. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/image.py +0 -0
  125. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/llm/deps.py +0 -0
  126. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/llm/tools.py +0 -0
  127. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/media.py +0 -0
  128. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/media_importer.py +0 -0
  129. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/people_families_cache.py +0 -0
  130. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/ratelimiter.py +0 -0
  131. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/__init__.py +0 -0
  132. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/bookmarks.py +0 -0
  133. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/chat.py +0 -0
  134. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/citations.py +0 -0
  135. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/config.py +0 -0
  136. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/db_backend.py +0 -0
  137. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/dna.py +0 -0
  138. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/emit.py +0 -0
  139. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/events.py +0 -0
  140. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/export_media.py +0 -0
  141. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/face_detection.py +0 -0
  142. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/facts.py +0 -0
  143. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/families.py +0 -0
  144. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/file.py +0 -0
  145. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/history.py +0 -0
  146. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/holidays.py +0 -0
  147. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/import_media.py +0 -0
  148. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/living.py +0 -0
  149. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/match.py +0 -0
  150. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/media.py +0 -0
  151. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/name_formats.py +0 -0
  152. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/name_groups.py +0 -0
  153. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/notes.py +0 -0
  154. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/objects.py +0 -0
  155. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/ocr.py +0 -0
  156. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/people.py +0 -0
  157. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/relations.py +0 -0
  158. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/repositories.py +0 -0
  159. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/restore.py +0 -0
  160. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/search.py +0 -0
  161. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/sort.py +0 -0
  162. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/sources.py +0 -0
  163. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/tags.py +0 -0
  164. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/tasks.py +0 -0
  165. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/timeline.py +0 -0
  166. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/transactions.py +0 -0
  167. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/translations.py +0 -0
  168. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/trees.py +0 -0
  169. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/types.py +0 -0
  170. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/verify.py +0 -0
  171. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/resources/ydna.py +0 -0
  172. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/s3.py +0 -0
  173. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/search/embeddings.py +0 -0
  174. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/search/metadata.py +0 -0
  175. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/search/text.py +0 -0
  176. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/search/text_semantic.py +0 -0
  177. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/api/telemetry.py +0 -0
  178. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/app.py +0 -0
  179. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/auth/oidc_helpers.py +0 -0
  180. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/auth/passwords.py +0 -0
  181. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/auth/sql_guid.py +0 -0
  182. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/celery.py +0 -0
  183. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/config.py +0 -0
  184. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/const.py +0 -0
  185. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/data/example_gramps_auth.cfg +0 -0
  186. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/data/face_detection_yunet_2023mar.onnx +0 -0
  187. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/data/test_auth.cfg +0 -0
  188. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/dbloader.py +0 -0
  189. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/dbmanager.py +0 -0
  190. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/logging_utils.py +0 -0
  191. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/profiler.py +0 -0
  192. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/sentry.py +0 -0
  193. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/static/index.html +0 -0
  194. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/templates/confirmation.html +0 -0
  195. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/templates/reset_password.html +0 -0
  196. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/templates/reset_password_base.html +0 -0
  197. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/templates/reset_password_error.html +0 -0
  198. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/translogger.py +0 -0
  199. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/types.py +0 -0
  200. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/undodb.py +0 -0
  201. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/util/__init__.py +0 -0
  202. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/util/celery.py +0 -0
  203. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/verify_lib.py +0 -0
  204. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi/wsgi.py +0 -0
  205. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi.egg-info/dependency_links.txt +0 -0
  206. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/gramps_webapi.egg-info/top_level.txt +0 -0
  207. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/mypy.ini +0 -0
  208. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/requirements-dev.txt +0 -0
  209. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/scripts/s3_rename.py +0 -0
  210. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/scripts/start_dev_container.sh +0 -0
  211. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/semantic_index.txt +0 -0
  212. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/setup.cfg +0 -0
  213. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/setup.py +0 -0
  214. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_check.py +0 -0
  215. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_cli.py +0 -0
  216. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_db.py +0 -0
  217. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_delete.py +0 -0
  218. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_dna_match_parser.py +0 -0
  219. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_embeddings.py +0 -0
  220. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/__init__.py +0 -0
  221. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/checks.py +0 -0
  222. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_anniversaries_ics.py +0 -0
  223. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_bookmarks.py +0 -0
  224. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_chat.py +0 -0
  225. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_citations.py +0 -0
  226. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_config.py +0 -0
  227. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_db_handle.py +0 -0
  228. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_delete.py +0 -0
  229. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_dna.py +0 -0
  230. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_events.py +0 -0
  231. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_facts.py +0 -0
  232. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_families.py +0 -0
  233. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_file.py +0 -0
  234. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_holidays.py +0 -0
  235. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_import_media.py +0 -0
  236. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_living.py +0 -0
  237. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_media.py +0 -0
  238. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_media_archive.py +0 -0
  239. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_merge.py +0 -0
  240. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_name_formats.py +0 -0
  241. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_name_groups.py +0 -0
  242. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_notes.py +0 -0
  243. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_object_query.py +0 -0
  244. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_object_query_where_tree.py +0 -0
  245. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_ocr.py +0 -0
  246. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_oidc.py +0 -0
  247. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_paging_sort.py +0 -0
  248. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_people_query.py +0 -0
  249. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_places.py +0 -0
  250. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_post.py +0 -0
  251. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_profiler.py +0 -0
  252. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_ready.py +0 -0
  253. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_relations.py +0 -0
  254. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_repair.py +0 -0
  255. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_repositories.py +0 -0
  256. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_s3.py +0 -0
  257. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_search.py +0 -0
  258. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_sources.py +0 -0
  259. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_tags.py +0 -0
  260. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_tasks.py +0 -0
  261. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_timelines.py +0 -0
  262. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_token.py +0 -0
  263. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_transactions.py +0 -0
  264. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_translations.py +0 -0
  265. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_trees.py +0 -0
  266. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_types.py +0 -0
  267. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_upload.py +0 -0
  268. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_verify.py +0 -0
  269. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/test_ydna.py +0 -0
  270. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_endpoints/util.py +0 -0
  271. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_file.py +0 -0
  272. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_image.py +0 -0
  273. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_jwt.py +0 -0
  274. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_limiter.py +0 -0
  275. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_llm_tools.py +0 -0
  276. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_logging_utils.py +0 -0
  277. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_map_tile.py +0 -0
  278. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_media_importer.py +0 -0
  279. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_media_prefix_tree.py +0 -0
  280. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_migrate_grampsdb.py +0 -0
  281. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_object_query_parsing.py +0 -0
  282. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_openapi_operation_ids.py +0 -0
  283. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_password.py +0 -0
  284. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_private_proxy.py +0 -0
  285. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_s3.py +0 -0
  286. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_search_metadata.py +0 -0
  287. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_sentry.py +0 -0
  288. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_sort_raw_data.py +0 -0
  289. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_sqlauth.py +0 -0
  290. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_task_error.py +0 -0
  291. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_tasks.py +0 -0
  292. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_telemetry.py +0 -0
  293. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_text.py +0 -0
  294. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_undodb.py +0 -0
  295. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_verify_lib.py +0 -0
  296. {gramps_webapi-3.22.2 → gramps_webapi-3.23.0}/tests/test_version.py +0 -0
@@ -5,8 +5,29 @@ on:
5
5
  pull_request:
6
6
 
7
7
  jobs:
8
+ typecheck:
9
+ runs-on: ubuntu-26.04
10
+ steps:
11
+ - uses: actions/checkout@v5
12
+ - name: Set up Python
13
+ uses: actions/setup-python@v6
14
+ with:
15
+ python-version: "3.10"
16
+ - name: Install Ubuntu dependencies
17
+ run: sudo apt update && sudo apt-get -y install gettext appstream pkg-config libcairo2-dev gir1.2-gtk-3.0 libgirepository1.0-dev libicu-dev libopencv-dev gir1.2-pango-1.0
18
+ - name: Install Python dependencies
19
+ run: |
20
+ python -m pip install --upgrade pip wheel setuptools
21
+ # CPU-only build: the default one pulls in ~3 GB of CUDA libraries
22
+ pip install torch --index-url https://download.pytorch.org/whl/cpu
23
+ pip install opencv-python
24
+ pip install -r requirements-dev.txt
25
+ pip install .[ai]
26
+ - name: Run type checker
27
+ run: mypy --install-types --non-interactive --ignore-missing-imports --exclude build .
28
+
8
29
  build:
9
- runs-on: ubuntu-24.04
30
+ runs-on: ubuntu-26.04
10
31
  strategy:
11
32
  fail-fast: true
12
33
  matrix:
@@ -33,10 +54,12 @@ jobs:
33
54
  restore-keys: |
34
55
  hf-models-
35
56
  - name: Install Ubuntu dependencies
36
- run: sudo apt update && sudo apt-get -y install gettext appstream pkg-config libcairo2-dev gir1.2-gtk-3.0 libgirepository1.0-dev libicu-dev libopencv-dev tesseract-ocr tesseract-ocr-all gir1.2-pango-1.0 poppler-utils
57
+ run: sudo apt update && sudo apt-get -y install gettext appstream pkg-config libcairo2-dev gir1.2-gtk-3.0 libgirepository1.0-dev libicu-dev libopencv-dev tesseract-ocr gir1.2-pango-1.0 poppler-utils
37
58
  - name: Install Python dependencies
38
59
  run: |
39
60
  python -m pip install --upgrade pip wheel setuptools
61
+ # CPU-only build: the default one pulls in ~3 GB of CUDA libraries
62
+ pip install torch --index-url https://download.pytorch.org/whl/cpu
40
63
  pip install opencv-python
41
64
  pip install -r requirements-dev.txt
42
65
  pip install .[ai]
@@ -59,15 +82,13 @@ jobs:
59
82
  alembic heads
60
83
  exit 1
61
84
  fi
62
- - name: Run type checker
63
- run: mypy --install-types --non-interactive --ignore-missing-imports --exclude build .
64
85
  - name: Test with pytest
65
86
  env:
66
87
  HF_HUB_OFFLINE: 1
67
88
  run: pytest -x
68
89
 
69
90
  test-base-image:
70
- runs-on: ubuntu-24.04
91
+ runs-on: ubuntu-26.04
71
92
  container: ghcr.io/gramps-project/gramps-web-base:latest
72
93
  steps:
73
94
  - uses: actions/checkout@v5
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gramps-webapi
3
- Version: 3.22.2
3
+ Version: 3.23.0
4
4
  Summary: A RESTful web API for the Gramps genealogical database.
5
5
  Author: Gramps Development Team
6
6
  Author-email: "David M. Straub" <straub@protonmail.com>
@@ -41,12 +41,11 @@ Requires-Dist: Unidecode
41
41
  Requires-Dist: pytesseract
42
42
  Requires-Dist: gramps-object-query-language<0.6,>=0.5.1
43
43
  Requires-Dist: gramps-ql>=0.5.0
44
- Requires-Dist: object-ql>=0.1.3
45
- Requires-Dist: sifts>=1.1.0
44
+ Requires-Dist: sifts>=1.4.0
46
45
  Requires-Dist: requests
47
46
  Requires-Dist: yclade>=0.5.0
48
47
  Requires-Dist: Authlib>=1.6.4
49
- Requires-Dist: gramps-gedcom7
48
+ Requires-Dist: gramps-gedcom7>=0.4.0
50
49
  Requires-Dist: flask-smorest
51
50
  Requires-Dist: typing-extensions>=4.13.0
52
51
  Provides-Extra: ai
@@ -0,0 +1,81 @@
1
+ """Allow several labelled access tokens per user and scope
2
+
3
+ Sync tokens are used one per device, and each device needs its own token so
4
+ that it can be revoked on its own. The unique constraint on (user_id, scope)
5
+ is replaced by one on (user_id, scope, label); scopes with a single token per
6
+ user keep using an empty label. Also record when a token was last used.
7
+
8
+ Revision ID: 7b2e9f4c1a63
9
+ Revises: d4e9a1c7b3f2
10
+ Create Date: 2026-10-03 00:00:00.000000
11
+
12
+ """
13
+
14
+ from alembic import op
15
+ import sqlalchemy as sa
16
+ from sqlalchemy.engine.reflection import Inspector
17
+
18
+ # revision identifiers, used by Alembic.
19
+ revision = "7b2e9f4c1a63"
20
+ down_revision = "d4e9a1c7b3f2"
21
+ branch_labels = None
22
+ depends_on = None
23
+
24
+ TABLE = "access_tokens"
25
+ OLD_CONSTRAINT = "uq_access_tokens_user_scope"
26
+ OLD_COLUMNS = ["user_id", "scope"]
27
+ NEW_CONSTRAINT = "uq_access_tokens_user_scope_label"
28
+ NEW_COLUMNS = ["user_id", "scope", "label"]
29
+ # lets batch mode refer to a constraint if SQLite reflects it without a name
30
+ NAMING_CONVENTION = {"uq": "uq_%(table_name)s_%(column_0_name)s"}
31
+
32
+
33
+ def _unique_constraint_name(
34
+ inspector: Inspector, columns: list[str], default: str
35
+ ) -> str | None:
36
+ """Return the name of the unique constraint on columns, or None if absent."""
37
+ for constraint in inspector.get_unique_constraints(TABLE):
38
+ if list(constraint["column_names"]) == columns:
39
+ return constraint["name"] or default
40
+ return None
41
+
42
+
43
+ def upgrade():
44
+ bind = op.get_bind()
45
+ inspector = Inspector.from_engine(bind)
46
+ columns = {column["name"] for column in inspector.get_columns(TABLE)}
47
+ old_name = _unique_constraint_name(inspector, OLD_COLUMNS, OLD_CONSTRAINT)
48
+ has_new = _unique_constraint_name(inspector, NEW_COLUMNS, NEW_CONSTRAINT)
49
+ # on SQLite, batch mode recreates the table, now with AUTOINCREMENT
50
+ with op.batch_alter_table(
51
+ TABLE,
52
+ naming_convention=NAMING_CONVENTION,
53
+ table_kwargs={"sqlite_autoincrement": True},
54
+ ) as batch_op:
55
+ if "label" not in columns:
56
+ batch_op.add_column(
57
+ sa.Column(
58
+ "label", sa.String(length=100), nullable=False, server_default=""
59
+ )
60
+ )
61
+ if "last_used_at" not in columns:
62
+ batch_op.add_column(sa.Column("last_used_at", sa.DateTime(), nullable=True))
63
+ if old_name:
64
+ batch_op.drop_constraint(old_name, type_="unique")
65
+ if not has_new:
66
+ batch_op.create_unique_constraint(NEW_CONSTRAINT, NEW_COLUMNS)
67
+
68
+
69
+ def downgrade():
70
+ bind = op.get_bind()
71
+ # the old schema allows a single token per user and scope, so labelled
72
+ # tokens (one per device) can't be kept
73
+ bind.execute(sa.text(f"DELETE FROM {TABLE} WHERE label != ''"))
74
+ inspector = Inspector.from_engine(bind)
75
+ new_name = _unique_constraint_name(inspector, NEW_COLUMNS, NEW_CONSTRAINT)
76
+ with op.batch_alter_table(TABLE, naming_convention=NAMING_CONVENTION) as batch_op:
77
+ if new_name:
78
+ batch_op.drop_constraint(new_name, type_="unique")
79
+ batch_op.create_unique_constraint(OLD_CONSTRAINT, OLD_COLUMNS)
80
+ batch_op.drop_column("last_used_at")
81
+ batch_op.drop_column("label")
@@ -19,4 +19,4 @@
19
19
 
20
20
  """Version information."""
21
21
 
22
- __version__ = "3.22.2"
22
+ __version__ = "3.23.0"
@@ -30,7 +30,11 @@ from .auth import jwt_required
30
30
  from .blueprint import api_blueprint
31
31
  from .cache import thumbnail_cache_decorator, tile_cache_decorator
32
32
  from .media import get_media_handler
33
- from .resources.access_tokens import UserAccessTokenResource
33
+ from .resources.access_tokens import (
34
+ UserAccessTokenItemResource,
35
+ UserAccessTokenListResource,
36
+ UserAccessTokenResource,
37
+ )
34
38
  from .resources.anniversaries import AnniversariesIcsResource
35
39
  from .resources.base import Resource, object_request_body
36
40
  from .resources.bookmarks import (
@@ -82,7 +86,11 @@ from .resources.merge import (
82
86
  MergeRepositoryResource,
83
87
  MergeSourceResource,
84
88
  )
85
- from .resources.metadata import MetadataResearcherResource, MetadataResource
89
+ from .resources.metadata import (
90
+ MetadataResearcherResource,
91
+ MetadataResource,
92
+ MetadataServerResource,
93
+ )
86
94
  from .resources.name_formats import NameFormatsResource
87
95
  from .resources.name_groups import NameGroupsResource
88
96
  from .resources.notes import NoteResource, NotesResource
@@ -112,7 +120,11 @@ from .resources.oidc import (
112
120
  OIDCTokenExchangeResource,
113
121
  )
114
122
  from .resources.people import PeopleResource, PersonResource
115
- from .resources.places import PlaceResource, PlacesResource
123
+ from .resources.places import (
124
+ PlaceCoordinatesResource,
125
+ PlaceResource,
126
+ PlacesResource,
127
+ )
116
128
  from .resources.relations import RelationResource, RelationsResource
117
129
  from .resources.reports import (
118
130
  ReportFileResource,
@@ -135,6 +147,7 @@ from .resources.token import (
135
147
  TokenCreateOwnerResource,
136
148
  TokenRefreshResource,
137
149
  TokenResource,
150
+ TokenSyncResource,
138
151
  )
139
152
  from .resources.transactions import TransactionsResource
140
153
  from .resources.translations import TranslationResource, TranslationsResource
@@ -281,6 +294,7 @@ register_endpt(
281
294
  # Token
282
295
  register_endpt(TokenResource, "/token/", "token", tags=["Token"])
283
296
  register_endpt(TokenRefreshResource, "/token/refresh/", "token_refresh", tags=["Token"])
297
+ register_endpt(TokenSyncResource, "/token/sync/", "token_sync", tags=["Token"])
284
298
  register_endpt(
285
299
  TokenCreateOwnerResource,
286
300
  "/token/create_owner/",
@@ -435,6 +449,12 @@ register_endpt(
435
449
  object_name="Place",
436
450
  )
437
451
  register_endpt(PlaceQueryResource, "/places/query/", "places-query", tags=["Places"])
452
+ register_endpt(
453
+ PlaceCoordinatesResource,
454
+ "/places/coordinates/",
455
+ "places-coordinates",
456
+ tags=["Places"],
457
+ )
438
458
  register_endpt(
439
459
  MergePlaceResource,
440
460
  "/places/<string:phoenix_handle>/merge/<string:titanic_handle>",
@@ -782,6 +802,12 @@ register_endpt(
782
802
  "metadata_researcher",
783
803
  tags=["Metadata"],
784
804
  )
805
+ register_endpt(
806
+ MetadataServerResource,
807
+ "/metadata/server/",
808
+ "metadata_server",
809
+ tags=["Metadata"],
810
+ )
785
811
  # Anniversaries
786
812
  register_endpt(
787
813
  AnniversariesIcsResource,
@@ -798,6 +824,18 @@ register_endpt(
798
824
  "user_access_token",
799
825
  tags=["Users"],
800
826
  )
827
+ register_endpt(
828
+ UserAccessTokenListResource,
829
+ "/users/-/access-tokens/<string:scope>/tokens/",
830
+ "user_access_token_list",
831
+ tags=["Users"],
832
+ )
833
+ register_endpt(
834
+ UserAccessTokenItemResource,
835
+ "/users/-/access-tokens/<string:scope>/tokens/<int:token_id>/",
836
+ "user_access_token_item",
837
+ tags=["Users"],
838
+ )
801
839
  register_endpt(
802
840
  UserRegisterResource,
803
841
  "/users/<string:user_name>/register/",
@@ -27,8 +27,9 @@ import mimetypes
27
27
  import os
28
28
  import uuid
29
29
  from pathlib import Path
30
- from typing import Dict, Optional
30
+ from typing import Callable, Dict, Optional
31
31
 
32
+ import gramps_gedcom7
32
33
  from celery import Task
33
34
  from flask import abort, current_app
34
35
  from gramps.gen import filters
@@ -46,6 +47,7 @@ from gramps.gen.user import User
46
47
  from gramps.gen.utils.resourcepath import ResourcePath
47
48
 
48
49
  from ..const import DISABLED_EXPORTERS
50
+ from .resources.util import truncate_import_report
49
51
  from .util import UserTaskProgress, abort_with_message, get_locale_for_language
50
52
 
51
53
  _ = glocale.translation.gettext
@@ -58,6 +60,16 @@ LIVING_FILTERS = {
58
60
  "ExcludeAll": LivingProxyDb.MODE_EXCLUDE_ALL,
59
61
  }
60
62
 
63
+ # GEDCOM 7 is exported by the gramps-gedcom7 library rather than a plugin. Its
64
+ # files end in .ged like GEDCOM 5.5.1, so it needs its own key in the API.
65
+ GEDCOM7_EXTENSION = "ged7"
66
+ GEDCOM7_EXPORTER = {
67
+ "name": "GEDCOM 7",
68
+ "description": "GEDCOM 7 is used to transfer data between genealogy programs.",
69
+ "extension": GEDCOM7_EXTENSION,
70
+ "module": "gramps_gedcom7",
71
+ }
72
+
61
73
  # mimetypes.init()
62
74
 
63
75
 
@@ -206,6 +218,8 @@ def get_exporters(extension: str | None = None):
206
218
  "module": plugin.get_module_name(),
207
219
  }
208
220
  exporters.append(exporter)
221
+ if extension is None or extension == GEDCOM7_EXTENSION:
222
+ exporters.append(GEDCOM7_EXPORTER)
209
223
  return exporters
210
224
 
211
225
 
@@ -265,13 +279,26 @@ def prepare_options(db_handle: DbReadBase, args: Dict):
265
279
 
266
280
 
267
281
  def run_export(
268
- db_handle: DbReadBase, extension: str, options, task: Optional[Task] = None
269
- ):
270
- """Generate the export."""
282
+ db_handle: DbReadBase,
283
+ extension: str,
284
+ options,
285
+ task: Optional[Task] = None,
286
+ progress: Callable[[int, int], None] | None = None,
287
+ ) -> tuple[str, str, list[str]]:
288
+ """Generate the export.
289
+
290
+ Plugin exporters report progress through `task`, the GEDCOM 7 exporter
291
+ through `progress`, called with the records done and the total.
292
+
293
+ Returns the file name, its extension, and the exporter's messages about
294
+ what it left out, if it reports any. Only the GEDCOM 7 exporter does.
295
+ """
271
296
  export_path = current_app.config.get("EXPORT_DIR")
272
297
  if not export_path:
273
298
  raise abort_with_message(500, "EXPORT_DIR not set in configuration")
274
299
  os.makedirs(export_path, exist_ok=True)
300
+ if extension == GEDCOM7_EXTENSION:
301
+ return run_gedcom7_export(db_handle, export_path, options, progress=progress)
275
302
  file_name = f"{uuid.uuid4()}.{extension}"
276
303
  file_path = os.path.join(export_path, file_name)
277
304
  _resources = ResourcePath()
@@ -297,5 +324,36 @@ def run_export(
297
324
  )
298
325
  if not result:
299
326
  abort_with_message(500, "Export function failed")
300
- return file_name, "." + extension
327
+ return file_name, "." + extension, []
301
328
  abort_with_message(404, "Exporter not found") # exporter not found
329
+
330
+
331
+ def run_gedcom7_export(
332
+ db_handle: DbReadBase,
333
+ export_path: str,
334
+ options,
335
+ progress: Callable[[int, int], None] | None = None,
336
+ ) -> tuple[str, str, list[str]]:
337
+ """Export the database as a GEDCOM 7 file."""
338
+ file_name = f"{uuid.uuid4()}.ged"
339
+ file_path = os.path.join(export_path, file_name)
340
+ filters.reload_custom_filters()
341
+ try:
342
+ report = gramps_gedcom7.export_gedcom(
343
+ options.get_filtered_database(db_handle), file_path, progress=progress
344
+ )
345
+ except gramps_gedcom7.MissingObjectsError as e:
346
+ abort_with_message(
347
+ 422,
348
+ f"The family tree cannot be exported: {e}. Running the check & "
349
+ "repair tool on the tree should fix this.",
350
+ )
351
+ except gramps_gedcom7.ExportValidationError as e:
352
+ if e.from_data:
353
+ abort_with_message(422, f"The family tree cannot be exported: {e}")
354
+ current_app.logger.exception("GEDCOM 7 export produced an invalid file")
355
+ abort_with_message(500, f"Export failed: {e}")
356
+ except Exception as e:
357
+ current_app.logger.exception("GEDCOM 7 export failed")
358
+ abort_with_message(500, f"Export failed: {e}")
359
+ return file_name, ".ged", truncate_import_report(report.messages())
@@ -7,6 +7,7 @@ from typing import Any
7
7
 
8
8
  import httpx
9
9
  from flask import current_app
10
+ from openai import OpenAIError
10
11
  from pydantic_ai import ModelMessagesTypeAdapter
11
12
  from pydantic_ai.exceptions import (
12
13
  ModelAPIError,
@@ -14,6 +15,7 @@ from pydantic_ai.exceptions import (
14
15
  ModelRetry,
15
16
  UnexpectedModelBehavior,
16
17
  UsageLimitExceeded,
18
+ UserError,
17
19
  )
18
20
  from pydantic_ai.messages import (
19
21
  ModelRequest,
@@ -131,13 +133,19 @@ def answer_with_agent(
131
133
  max_tokens = config.get("LLM_MAX_TOKENS", 200_000)
132
134
 
133
135
  if not model_name:
134
- raise ValueError("No LLM model specified")
136
+ logger.error("No LLM model specified")
137
+ abort_with_message(500, "The AI model is not configured correctly.")
135
138
 
136
- agent = create_agent(
137
- model_name=model_name,
138
- base_url=base_url,
139
- system_prompt_override=system_prompt_override,
140
- )
139
+ try:
140
+ agent = create_agent(
141
+ model_name=model_name,
142
+ base_url=base_url,
143
+ system_prompt_override=system_prompt_override,
144
+ )
145
+ except (UserError, ValueError, OpenAIError) as e:
146
+ # e.g. unknown provider prefix or missing provider API key/base URL
147
+ logger.error("Failed to set up the AI model '%s': %s", model_name, e)
148
+ abort_with_message(500, "The AI model is not configured correctly.")
141
149
 
142
150
  deps = AgentDeps(
143
151
  tree=tree,
@@ -150,7 +158,9 @@ def answer_with_agent(
150
158
  message_history: list[ModelRequest | ModelResponse] = []
151
159
  if message_history_raw:
152
160
  try:
153
- message_history = ModelMessagesTypeAdapter.validate_json(message_history_raw)
161
+ message_history = ModelMessagesTypeAdapter.validate_json(
162
+ message_history_raw
163
+ )
154
164
  except Exception as e: # pylint: disable=broad-except
155
165
  raise ValueError(f"Invalid message_history_raw: {e}") from e
156
166
  elif history:
@@ -186,12 +196,15 @@ def answer_with_agent(
186
196
  return result
187
197
  except UsageLimitExceeded as e:
188
198
  logger.warning("Agent usage limit exceeded: %s", e)
189
- abort_with_message(429, "The AI agent exceeded its usage limits for this request.")
199
+ abort_with_message(
200
+ 429, "The AI agent exceeded its usage limits for this request."
201
+ )
190
202
  except ModelHTTPError as e:
191
203
  logger.error("Model provider returned an error: %s", e)
192
204
  if e.status_code == 429:
193
205
  abort_with_message(
194
- 429, "The AI model provider is rate limiting requests. Please try again later."
206
+ 429,
207
+ "The AI model provider is rate limiting requests. Please try again later.",
195
208
  )
196
209
  if e.status_code in (502, 503, 504, 529):
197
210
  # 529 is Anthropic's "overloaded"
@@ -199,7 +212,9 @@ def answer_with_agent(
199
212
  abort_with_message(502, "The AI model provider returned an error.")
200
213
  except httpx.TimeoutException as e:
201
214
  logger.error("Model provider request timed out: %r", e)
202
- abort_with_message(504, "The AI model did not respond in time. Please try again.")
215
+ abort_with_message(
216
+ 504, "The AI model did not respond in time. Please try again."
217
+ )
203
218
  except (ModelAPIError, httpx.TransportError) as e:
204
219
  # connection refused, DNS failure, protocol error, or an SDK-wrapped equivalent
205
220
  logger.error("Model provider request failed: %r", e)
@@ -21,10 +21,15 @@
21
21
 
22
22
  from __future__ import annotations
23
23
 
24
+ import warnings
25
+
24
26
  from pydantic_ai import Agent, RunContext
25
27
  from pydantic_ai.capabilities import ProcessHistory
26
28
  from pydantic_ai.messages import ModelMessage, ModelRequest, UserPromptPart
29
+ from pydantic_ai.models.ollama import OllamaModel
27
30
  from pydantic_ai.models.openai import OpenAIChatModel
31
+ from pydantic_ai.providers import infer_provider_class
32
+ from pydantic_ai.providers.ollama import OllamaProvider
28
33
  from pydantic_ai.providers.openai import OpenAIProvider
29
34
 
30
35
  from .deps import AgentDeps
@@ -125,6 +130,25 @@ FORMATTING
125
130
  Use Markdown freely. When tool results contain links like [Name](/person/I0044), include them in your response exactly as they appear — never modify the path and never drop the link. Every person, family, event, place, source, citation, repository, note, and media object should be linked."""
126
131
 
127
132
 
133
+ def _has_provider_prefix(model_name: str) -> bool:
134
+ """Whether the model name starts with a provider known to Pydantic AI.
135
+
136
+ A colon alone is not enough: Ollama model names like "qwen2.5:7b" use it
137
+ to separate the model from its tag.
138
+ """
139
+ if ":" not in model_name:
140
+ return False
141
+ provider_name = model_name.split(":", maxsplit=1)[0]
142
+ try:
143
+ with warnings.catch_warnings():
144
+ # deprecation warnings are emitted again when the model is created
145
+ warnings.simplefilter("ignore")
146
+ infer_provider_class(provider_name)
147
+ except ValueError:
148
+ return False
149
+ return True
150
+
151
+
128
152
  def create_agent(
129
153
  model_name: str,
130
154
  base_url: str | None = None,
@@ -133,13 +157,14 @@ def create_agent(
133
157
  """Create a Pydantic AI agent with the specified model.
134
158
 
135
159
  Args:
136
- model_name: The name of the LLM model to use. If it contains a colon (e.g.,
137
- "mistral:mistral-large-latest" or "openai:gpt-4"), it will be treated
138
- as a provider-prefixed model name and Pydantic AI will handle provider
139
- detection automatically. Otherwise, it will be treated as an OpenAI
140
- compatible model name.
141
- base_url: Optional base URL for the OpenAI-compatible API (ignored if
142
- model_name contains a provider prefix)
160
+ model_name: The name of the LLM model to use. If it starts with a provider
161
+ prefix known to Pydantic AI (e.g., "mistral:mistral-large-latest" or
162
+ "openai:gpt-4"), Pydantic AI will handle provider detection
163
+ automatically. Otherwise (e.g., "gpt-4" or "qwen2.5:7b"), it will be
164
+ treated as an OpenAI compatible model name.
165
+ base_url: Optional base URL for the OpenAI-compatible API. Ignored if
166
+ model_name contains a provider prefix, except for "ollama:", where
167
+ it takes precedence over the OLLAMA_BASE_URL environment variable.
143
168
  system_prompt_override: Optional override for the system prompt
144
169
 
145
170
  Returns:
@@ -147,8 +172,13 @@ def create_agent(
147
172
  """
148
173
  # If model name has a provider prefix (e.g., "mistral:model-name"),
149
174
  # let Pydantic AI handle provider detection automatically
150
- if ":" in model_name:
151
- model: str | OpenAIChatModel = model_name
175
+ if model_name.startswith("ollama:") and base_url:
176
+ model: str | OpenAIChatModel = OllamaModel(
177
+ model_name.removeprefix("ollama:"),
178
+ provider=OllamaProvider(base_url=base_url),
179
+ )
180
+ elif _has_provider_prefix(model_name):
181
+ model = model_name
152
182
  else:
153
183
  # Otherwise, use OpenAI-compatible provider with optional base_url
154
184
  provider = OpenAIProvider(base_url=base_url)