gramps-webapi 3.22.0__tar.gz → 3.22.2__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.
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/Dockerfile.base +3 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/PKG-INFO +2 -2
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/ROADMAP.md +4 -4
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/_version.py +1 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/__init__.py +50 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/deprecations.py +4 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/image.py +31 -2
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/media.py +1 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/report.py +17 -2
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/base.py +55 -10
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/filters.py +86 -10
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/import_media.py +10 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/importers.py +30 -27
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/metadata.py +8 -2
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/schemas.py +3 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/sort.py +65 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/token.py +15 -3
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/transactions.py +39 -5
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/trees.py +1 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/tasks.py +30 -23
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/util.py +11 -3
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/auth/__init__.py +68 -42
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/auth/const.py +9 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/profiler.py +237 -39
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi.egg-info/PKG-INFO +2 -2
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi.egg-info/SOURCES.txt +4 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi.egg-info/requires.txt +1 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/pyproject.toml +1 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/requirements-dev.txt +1 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_deprecations.py +13 -0
- gramps_webapi-3.22.2/tests/test_endpoints/test_db_handle.py +72 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_filters.py +253 -63
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_import_media.py +17 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_importers.py +34 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_metadata.py +44 -3
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_ocr.py +8 -1
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_oidc.py +74 -0
- gramps_webapi-3.22.2/tests/test_endpoints/test_paging_sort.py +163 -0
- gramps_webapi-3.22.2/tests/test_endpoints/test_profiler.py +70 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_reports.py +10 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_s3.py +2 -2
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_token.py +52 -2
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_transactions.py +74 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_image.py +95 -2
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_openapi_operation_ids.py +31 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_s3.py +2 -2
- gramps_webapi-3.22.2/tests/test_sort_raw_data.py +119 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_tasks.py +16 -4
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.devcontainer/Dockerfile +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.devcontainer/devcontainer.json +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.devcontainer/docker-compose.yml +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.dockerignore +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.editorconfig +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.flake8 +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.github/workflows/base-image.yml +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.github/workflows/image.yml +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.github/workflows/pypi.yml +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.github/workflows/test.yml +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.gitignore +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.pre-commit-config.yaml +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.pylintrc +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.vscode/extensions.json +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.vscode/launch.json +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.vscode/settings.json +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/.vscode/tasks.json +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/CLAUDE.md +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/CODE_OF_CONDUCT.md +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/CONTRIBUTING.md +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/Dockerfile +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/LICENSE +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/MANIFEST.in +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/README.md +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic.ini +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/README +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/env.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/script.py.mako +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/2082445b0769_add_oidc_accounts_table_for_secure_.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/22c8d1fba959_add_trees_enabled.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/66e56620891a_add_trees_table.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/6d8f3cb50b71_add_persistent_access_tokens_table.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/84960b7d968c_use_bigint_for_quota_media.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/a8e57fe0d82e_add_columns_for_ai_quota.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/b0582f54029c_use_bigint_for_usage_media.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/b2c3d4e5f6a7_add_tree_config_column.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/c1d2e3f4a5b6_merge_task_tree_and_tree_config.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/c89728e71264_.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/d4e9a1c7b3f2_drop_unique_constraint_on_user_email.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/e176543c72a8_add_tree_column_to_user.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/e5e738d09fa7_added_configuration_table.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/alembic_users/versions/f3a1c8e92b47_add_task_tree_table.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/docker-entrypoint.sh +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/__init__.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/__main__.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/auth.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/blueprint.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/cache.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/check.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/dna.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/emails.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/export.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/file.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/html.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/llm/__init__.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/llm/agent.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/llm/deps.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/llm/tools.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/media_importer.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/people_families_cache.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/ratelimiter.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/__init__.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/access_tokens.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/anniversaries.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/bookmarks.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/chat.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/citations.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/config.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/db_backend.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/delete.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/dna.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/emit.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/events.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/export_media.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/exporters.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/face_detection.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/facts.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/families.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/file.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/history.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/holidays.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/living.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/match.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/media.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/merge.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/name_formats.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/name_groups.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/notes.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/object_query.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/objects.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/ocr.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/oidc.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/people.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/places.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/relations.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/reports.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/repositories.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/restore.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/search.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/sources.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/tags.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/tasks.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/timeline.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/translations.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/types.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/user.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/util.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/verify.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/resources/ydna.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/s3.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/search/__init__.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/search/embeddings.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/search/indexer.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/search/metadata.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/search/text.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/search/text_semantic.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/api/telemetry.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/app.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/auth/oidc.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/auth/oidc_helpers.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/auth/passwords.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/auth/sql_guid.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/celery.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/config.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/const.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/data/empty_gramps_auth.cfg +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/data/example_gramps_auth.cfg +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/data/face_detection_yunet_2023mar.onnx +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/data/test_auth.cfg +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/dbloader.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/dbmanager.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/logging_utils.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/sentry.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/static/index.html +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/templates/confirmation.html +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/templates/reset_password.html +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/templates/reset_password_base.html +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/templates/reset_password_error.html +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/translogger.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/types.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/undodb.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/util/__init__.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/util/celery.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/verify_lib.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi/wsgi.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi.egg-info/dependency_links.txt +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/gramps_webapi.egg-info/top_level.txt +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/mypy.ini +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/scripts/s3_rename.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/scripts/start_dev_container.sh +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/semantic_index.txt +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/setup.cfg +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/setup.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/__init__.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_check.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_cli.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_db.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_delete.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_dna_match_parser.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_embeddings.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/__init__.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/checks.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_access_tokens.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_anniversaries_ics.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_bookmarks.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_chat.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_citations.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_config.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_delete.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_dna.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_events.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_exporters.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_facts.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_families.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_file.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_history.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_holidays.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_living.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_media.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_media_archive.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_merge.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_name_formats.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_name_groups.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_notes.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_object_query.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_object_query_where_tree.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_people.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_people_query.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_places.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_post.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_put.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_ready.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_relations.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_repair.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_repositories.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_restore.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_search.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_sources.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_tags.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_tasks.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_timelines.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_translations.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_treeless_admin.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_trees.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_types.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_upload.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_user.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_verify.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/test_ydna.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_endpoints/util.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_file.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_import_util.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_jwt.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_limiter.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_llm_errors.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_llm_tools.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_logging_utils.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_map_tile.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_media_importer.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_media_prefix_tree.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_migrate_grampsdb.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_object_query_parsing.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_oidc_auth.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_password.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_private_proxy.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_search_metadata.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_sentry.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_sqlauth.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_task_error.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_telemetry.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_text.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_undodb.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_util.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_verify_lib.py +0 -0
- {gramps_webapi-3.22.0 → gramps_webapi-3.22.2}/tests/test_version.py +0 -0
|
@@ -8,7 +8,10 @@ WORKDIR /app
|
|
|
8
8
|
# install poppler (needed for PDF thumbnails)
|
|
9
9
|
# ffmpeg (needed for video thumbnails)
|
|
10
10
|
# postgresql client (needed for PostgreSQL backend)
|
|
11
|
+
# Noto fonts (needed to render non-Latin scripts in generated reports -
|
|
12
|
+
# without them, Pango has no glyphs and PDF output is garbled)
|
|
11
13
|
RUN apt-get update && apt-get install -y \
|
|
14
|
+
fonts-dejavu-core fonts-noto-core fonts-noto-cjk \
|
|
12
15
|
appstream pkg-config libcairo2-dev \
|
|
13
16
|
gir1.2-gtk-3.0 libgirepository1.0-dev libicu-dev \
|
|
14
17
|
graphviz gir1.2-gexiv2-0.10 gir1.2-osmgpsmap-1.0 \
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gramps-webapi
|
|
3
|
-
Version: 3.22.
|
|
3
|
+
Version: 3.22.2
|
|
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>
|
|
@@ -39,7 +39,7 @@ Requires-Dist: alembic
|
|
|
39
39
|
Requires-Dist: celery[redis]
|
|
40
40
|
Requires-Dist: Unidecode
|
|
41
41
|
Requires-Dist: pytesseract
|
|
42
|
-
Requires-Dist: gramps-object-query-language<0.6,>=0.5.
|
|
42
|
+
Requires-Dist: gramps-object-query-language<0.6,>=0.5.1
|
|
43
43
|
Requires-Dist: gramps-ql>=0.5.0
|
|
44
44
|
Requires-Dist: object-ql>=0.1.3
|
|
45
45
|
Requires-Dist: sifts>=1.1.0
|
|
@@ -13,14 +13,14 @@ removed no earlier than the next major.
|
|
|
13
13
|
|
|
14
14
|
| Version | Target | Gramps | Breaking changes |
|
|
15
15
|
|---|---|---|---|
|
|
16
|
-
| 3.
|
|
17
|
-
| 3.
|
|
16
|
+
| 3.22.x | current | 6.0 | none |
|
|
17
|
+
| 3.23.0 | October 2026 | 6.0 | none |
|
|
18
18
|
| 3.x | as needed | 6.0 | none |
|
|
19
19
|
| 4.0.0 | TBD | 6.0, 6.1 or 6.2 (open) | multi-tree by default, removals below |
|
|
20
20
|
|
|
21
|
-
## Notable in 3.
|
|
21
|
+
## Notable in 3.23
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
TBD
|
|
24
24
|
|
|
25
25
|
## Breaking in 4.0
|
|
26
26
|
|
|
@@ -176,6 +176,7 @@ def register_endpt(
|
|
|
176
176
|
name: str,
|
|
177
177
|
tags: Optional[List[str]] = None,
|
|
178
178
|
request_body: Optional[dict] = None,
|
|
179
|
+
object_name: Optional[str] = None,
|
|
179
180
|
):
|
|
180
181
|
"""Register an endpoint.
|
|
181
182
|
|
|
@@ -188,6 +189,11 @@ def register_endpt(
|
|
|
188
189
|
`request_body` documents the JSON payload of a POST/PUT endpoint (e.g.
|
|
189
190
|
via `object_request_body()`); omit it for endpoints that take no body
|
|
190
191
|
or a non-JSON one (such as Media's raw file upload).
|
|
192
|
+
|
|
193
|
+
`object_name` names the Gramps object type (e.g. "Person") used to
|
|
194
|
+
build CRUD summary text ("Get a Gramps Person record", ...). Pass it
|
|
195
|
+
for endpoints that operate on a single typed object; omit it for
|
|
196
|
+
endpoints (transactions, tokens, queries, ...) that don't.
|
|
191
197
|
"""
|
|
192
198
|
operation_name = name.replace("-", "_")
|
|
193
199
|
for verb in ("get", "post", "put", "delete", "patch"):
|
|
@@ -195,6 +201,24 @@ def register_endpt(
|
|
|
195
201
|
if method is None:
|
|
196
202
|
continue
|
|
197
203
|
doc_kwargs: dict = {"operationId": f"{verb}_{operation_name}"}
|
|
204
|
+
if object_name is not None:
|
|
205
|
+
collection_name = name.replace("-", " ").replace("_", " ")
|
|
206
|
+
if verb == "get":
|
|
207
|
+
summary = (
|
|
208
|
+
f"Get a Gramps {object_name} record"
|
|
209
|
+
if "<string:handle>" in url
|
|
210
|
+
else f"List Gramps {collection_name} with filters"
|
|
211
|
+
)
|
|
212
|
+
elif verb == "post":
|
|
213
|
+
summary = f"Create a Gramps {object_name} record"
|
|
214
|
+
elif verb == "put":
|
|
215
|
+
summary = f"Update a Gramps {object_name} record"
|
|
216
|
+
elif verb == "delete":
|
|
217
|
+
summary = f"Delete a Gramps {object_name} record"
|
|
218
|
+
else:
|
|
219
|
+
summary = None
|
|
220
|
+
if summary is not None:
|
|
221
|
+
doc_kwargs["summary"] = summary
|
|
198
222
|
if request_body is not None and verb in ("post", "put"):
|
|
199
223
|
doc_kwargs["requestBody"] = request_body
|
|
200
224
|
# setattr rather than plain assignment: mypy rejects assigning to a
|
|
@@ -295,6 +319,7 @@ register_endpt(
|
|
|
295
319
|
"person",
|
|
296
320
|
tags=["People"],
|
|
297
321
|
request_body=object_request_body("Person"),
|
|
322
|
+
object_name="Person",
|
|
298
323
|
)
|
|
299
324
|
register_endpt(
|
|
300
325
|
PersonDnaMatchesResource,
|
|
@@ -311,6 +336,7 @@ register_endpt(
|
|
|
311
336
|
"people",
|
|
312
337
|
tags=["People"],
|
|
313
338
|
request_body=object_request_body("Person"),
|
|
339
|
+
object_name="Person",
|
|
314
340
|
)
|
|
315
341
|
register_endpt(PersonQueryResource, "/people/query/", "people-query", tags=["People"])
|
|
316
342
|
register_endpt(
|
|
@@ -332,6 +358,7 @@ register_endpt(
|
|
|
332
358
|
"family",
|
|
333
359
|
tags=["Families"],
|
|
334
360
|
request_body=object_request_body("Family"),
|
|
361
|
+
object_name="Family",
|
|
335
362
|
)
|
|
336
363
|
register_endpt(
|
|
337
364
|
FamiliesResource,
|
|
@@ -339,6 +366,7 @@ register_endpt(
|
|
|
339
366
|
"families",
|
|
340
367
|
tags=["Families"],
|
|
341
368
|
request_body=object_request_body("Family"),
|
|
369
|
+
object_name="Family",
|
|
342
370
|
)
|
|
343
371
|
register_endpt(
|
|
344
372
|
FamilyQueryResource, "/families/query/", "families-query", tags=["Families"]
|
|
@@ -362,6 +390,7 @@ register_endpt(
|
|
|
362
390
|
"event",
|
|
363
391
|
tags=["Events"],
|
|
364
392
|
request_body=object_request_body("Event"),
|
|
393
|
+
object_name="Event",
|
|
365
394
|
)
|
|
366
395
|
register_endpt(
|
|
367
396
|
EventsResource,
|
|
@@ -369,6 +398,7 @@ register_endpt(
|
|
|
369
398
|
"events",
|
|
370
399
|
tags=["Events"],
|
|
371
400
|
request_body=object_request_body("Event"),
|
|
401
|
+
object_name="Event",
|
|
372
402
|
)
|
|
373
403
|
register_endpt(EventQueryResource, "/events/query/", "events-query", tags=["Events"])
|
|
374
404
|
register_endpt(
|
|
@@ -394,6 +424,7 @@ register_endpt(
|
|
|
394
424
|
"place",
|
|
395
425
|
tags=["Places"],
|
|
396
426
|
request_body=object_request_body("Place"),
|
|
427
|
+
object_name="Place",
|
|
397
428
|
)
|
|
398
429
|
register_endpt(
|
|
399
430
|
PlacesResource,
|
|
@@ -401,6 +432,7 @@ register_endpt(
|
|
|
401
432
|
"places",
|
|
402
433
|
tags=["Places"],
|
|
403
434
|
request_body=object_request_body("Place"),
|
|
435
|
+
object_name="Place",
|
|
404
436
|
)
|
|
405
437
|
register_endpt(PlaceQueryResource, "/places/query/", "places-query", tags=["Places"])
|
|
406
438
|
register_endpt(
|
|
@@ -416,6 +448,7 @@ register_endpt(
|
|
|
416
448
|
"citation",
|
|
417
449
|
tags=["Citations"],
|
|
418
450
|
request_body=object_request_body("Citation"),
|
|
451
|
+
object_name="Citation",
|
|
419
452
|
)
|
|
420
453
|
register_endpt(
|
|
421
454
|
CitationsResource,
|
|
@@ -423,6 +456,7 @@ register_endpt(
|
|
|
423
456
|
"citations",
|
|
424
457
|
tags=["Citations"],
|
|
425
458
|
request_body=object_request_body("Citation"),
|
|
459
|
+
object_name="Citation",
|
|
426
460
|
)
|
|
427
461
|
register_endpt(
|
|
428
462
|
CitationQueryResource, "/citations/query/", "citations-query", tags=["Citations"]
|
|
@@ -440,6 +474,7 @@ register_endpt(
|
|
|
440
474
|
"source",
|
|
441
475
|
tags=["Sources"],
|
|
442
476
|
request_body=object_request_body("Source"),
|
|
477
|
+
object_name="Source",
|
|
443
478
|
)
|
|
444
479
|
register_endpt(
|
|
445
480
|
SourcesResource,
|
|
@@ -447,6 +482,7 @@ register_endpt(
|
|
|
447
482
|
"sources",
|
|
448
483
|
tags=["Sources"],
|
|
449
484
|
request_body=object_request_body("Source"),
|
|
485
|
+
object_name="Source",
|
|
450
486
|
)
|
|
451
487
|
register_endpt(
|
|
452
488
|
SourceQueryResource, "/sources/query/", "sources-query", tags=["Sources"]
|
|
@@ -464,6 +500,7 @@ register_endpt(
|
|
|
464
500
|
"repository",
|
|
465
501
|
tags=["Repositories"],
|
|
466
502
|
request_body=object_request_body("Repository"),
|
|
503
|
+
object_name="Repository",
|
|
467
504
|
)
|
|
468
505
|
register_endpt(
|
|
469
506
|
RepositoriesResource,
|
|
@@ -471,6 +508,7 @@ register_endpt(
|
|
|
471
508
|
"repositories",
|
|
472
509
|
tags=["Repositories"],
|
|
473
510
|
request_body=object_request_body("Repository"),
|
|
511
|
+
object_name="Repository",
|
|
474
512
|
)
|
|
475
513
|
register_endpt(
|
|
476
514
|
RepositoryQueryResource,
|
|
@@ -491,10 +529,17 @@ register_endpt(
|
|
|
491
529
|
"media_object",
|
|
492
530
|
tags=["Media"],
|
|
493
531
|
request_body=object_request_body("Media"),
|
|
532
|
+
object_name="Media",
|
|
494
533
|
)
|
|
495
534
|
# MediaObjectsResource's POST takes a raw file upload (multipart), not JSON,
|
|
496
535
|
# so it gets no request_body doc here.
|
|
497
|
-
register_endpt(
|
|
536
|
+
register_endpt(
|
|
537
|
+
MediaObjectsResource,
|
|
538
|
+
"/media/",
|
|
539
|
+
"media_objects",
|
|
540
|
+
tags=["Media"],
|
|
541
|
+
object_name="Media",
|
|
542
|
+
)
|
|
498
543
|
register_endpt(MediaQueryResource, "/media/query/", "media-query", tags=["Media"])
|
|
499
544
|
register_endpt(
|
|
500
545
|
MergeMediaResource,
|
|
@@ -509,6 +554,7 @@ register_endpt(
|
|
|
509
554
|
"note",
|
|
510
555
|
tags=["Notes"],
|
|
511
556
|
request_body=object_request_body("Note"),
|
|
557
|
+
object_name="Note",
|
|
512
558
|
)
|
|
513
559
|
register_endpt(
|
|
514
560
|
NotesResource,
|
|
@@ -516,6 +562,7 @@ register_endpt(
|
|
|
516
562
|
"notes",
|
|
517
563
|
tags=["Notes"],
|
|
518
564
|
request_body=object_request_body("Note"),
|
|
565
|
+
object_name="Note",
|
|
519
566
|
)
|
|
520
567
|
register_endpt(NoteQueryResource, "/notes/query/", "notes-query", tags=["Notes"])
|
|
521
568
|
register_endpt(
|
|
@@ -531,6 +578,7 @@ register_endpt(
|
|
|
531
578
|
"tag",
|
|
532
579
|
tags=["Tags"],
|
|
533
580
|
request_body=object_request_body("Tag"),
|
|
581
|
+
object_name="Tag",
|
|
534
582
|
)
|
|
535
583
|
register_endpt(
|
|
536
584
|
TagsResource,
|
|
@@ -538,6 +586,7 @@ register_endpt(
|
|
|
538
586
|
"tags",
|
|
539
587
|
tags=["Tags"],
|
|
540
588
|
request_body=object_request_body("Tag"),
|
|
589
|
+
object_name="Tag",
|
|
541
590
|
)
|
|
542
591
|
register_endpt(TagQueryResource, "/tags/query/", "tags-query", tags=["Tags"])
|
|
543
592
|
# Trees
|
|
@@ -96,9 +96,12 @@ def check_deprecations(
|
|
|
96
96
|
_deprecation(
|
|
97
97
|
"SEARCH_INDEX_DIR",
|
|
98
98
|
"SEARCH_INDEX_DB_URI",
|
|
99
|
+
# the configured directory is deliberately not interpolated here:
|
|
100
|
+
# the message is exposed via the API, so it must not disclose a
|
|
101
|
+
# server filesystem path
|
|
99
102
|
"The `SEARCH_INDEX_DIR` config option is deprecated. Please use"
|
|
100
103
|
" `SEARCH_INDEX_DB_URI` instead, e.g. setting it to"
|
|
101
|
-
|
|
104
|
+
" `sqlite:///<search index directory>/search_index.db`.",
|
|
102
105
|
)
|
|
103
106
|
)
|
|
104
107
|
if (
|
|
@@ -29,7 +29,7 @@ from importlib.resources import as_file, files
|
|
|
29
29
|
from pathlib import Path
|
|
30
30
|
from typing import BinaryIO, Callable, Iterator, Union
|
|
31
31
|
|
|
32
|
-
from PIL import Image, ImageOps, UnidentifiedImageError
|
|
32
|
+
from PIL import Image, ImageCms, ImageOps, UnidentifiedImageError
|
|
33
33
|
from PIL.Image import DecompressionBombError
|
|
34
34
|
from PIL.Image import Image as ImageType
|
|
35
35
|
|
|
@@ -123,7 +123,36 @@ def save_image_buffer(image: ImageType, fmt="AVIF") -> BinaryIO:
|
|
|
123
123
|
if image.mode == "RGBA" and not supports_alpha:
|
|
124
124
|
image = image.convert("RGB")
|
|
125
125
|
elif image.mode not in ("RGB", "RGBA"):
|
|
126
|
-
image
|
|
126
|
+
if image.mode == "L" and image.info.get("icc_profile"):
|
|
127
|
+
# `convert("RGB")` changes the pixel mode but keeps the source
|
|
128
|
+
# ICC profile as-is, which is invalid for RGB output and gets
|
|
129
|
+
# embedded verbatim on save, producing files some decoders
|
|
130
|
+
# (e.g. Chrome for AVIF) refuse to open.
|
|
131
|
+
try:
|
|
132
|
+
source_profile = ImageCms.ImageCmsProfile(
|
|
133
|
+
io.BytesIO(image.info["icc_profile"])
|
|
134
|
+
)
|
|
135
|
+
transformed_image = ImageCms.profileToProfile(
|
|
136
|
+
image,
|
|
137
|
+
source_profile,
|
|
138
|
+
ImageCms.createProfile("sRGB"),
|
|
139
|
+
outputMode="RGB",
|
|
140
|
+
)
|
|
141
|
+
# `profileToProfile()` only returns `None` when called with
|
|
142
|
+
# `inPlace=True`, which we don't do here.
|
|
143
|
+
assert transformed_image is not None
|
|
144
|
+
image = transformed_image
|
|
145
|
+
except ImageCms.PyCMSError:
|
|
146
|
+
# The embedded profile is untrusted (uploaded) data: it can be
|
|
147
|
+
# malformed enough to open but still fail to build a color
|
|
148
|
+
# transform. Fall back to an uncolor-managed conversion rather
|
|
149
|
+
# than crashing.
|
|
150
|
+
image = image.convert("RGB")
|
|
151
|
+
image.info.pop("icc_profile", None)
|
|
152
|
+
else:
|
|
153
|
+
image = image.convert("RGB")
|
|
154
|
+
# the profile (if any) belonged to the previous color space
|
|
155
|
+
image.info.pop("icc_profile", None)
|
|
127
156
|
image.save(buffer, format=fmt)
|
|
128
157
|
buffer.seek(0)
|
|
129
158
|
return buffer
|
|
@@ -390,6 +390,7 @@ def check_quota_media(
|
|
|
390
390
|
usage_dict = get_tree_usage(tree)
|
|
391
391
|
assert usage_dict is not None, "Unexpected error while looking up usage data."
|
|
392
392
|
usage = usage_dict["usage_media"]
|
|
393
|
+
assert usage is not None, "Unexpected error while looking up usage data."
|
|
393
394
|
quota = usage_dict.get("quota_media")
|
|
394
395
|
if quota is None:
|
|
395
396
|
return
|
|
@@ -59,7 +59,6 @@ from gramps.gen.plug.report import (
|
|
|
59
59
|
CATEGORY_TEXT,
|
|
60
60
|
CATEGORY_TREE,
|
|
61
61
|
)
|
|
62
|
-
from gramps.gen.utils.grampslocale import GrampsLocale
|
|
63
62
|
from gramps.gen.utils.resourcepath import ResourcePath
|
|
64
63
|
|
|
65
64
|
from ..const import MIME_TYPES, REPORT_DEFAULTS, REPORT_FILTERS
|
|
@@ -328,7 +327,11 @@ def cl_report_new(
|
|
|
328
327
|
):
|
|
329
328
|
clr.option_class.handler.doc.set_css_filename(clr.css_filename)
|
|
330
329
|
my_report = report_class(database, clr.option_class, User())
|
|
331
|
-
|
|
330
|
+
if language:
|
|
331
|
+
# Only reached for reports that have no "trans" option. Reports that do
|
|
332
|
+
# have one have already applied the language in their constructor, and
|
|
333
|
+
# setting it here would discard the option value chosen by the user.
|
|
334
|
+
my_report.set_locale(language)
|
|
332
335
|
my_report.doc.init()
|
|
333
336
|
my_report.begin_report()
|
|
334
337
|
my_report.write_report()
|
|
@@ -368,6 +371,18 @@ def run_report(
|
|
|
368
371
|
report_options["of"] = os.path.join(report_path, file_name)
|
|
369
372
|
report_profile = get_report_profile(db_handle, plugin_manager, report_data)
|
|
370
373
|
validate_options(report_profile, report_options, allow_file=allow_file)
|
|
374
|
+
if (
|
|
375
|
+
language
|
|
376
|
+
and "trans" in report_profile["options_dict"]
|
|
377
|
+
and language in glocale.get_language_dict().values()
|
|
378
|
+
):
|
|
379
|
+
# Apply the language via the report's own localization option,
|
|
380
|
+
# which is evaluated in the report's constructor. Reports build
|
|
381
|
+
# locale-dependent objects there, so setting the locale
|
|
382
|
+
# afterwards is not enough. Languages the option does not accept
|
|
383
|
+
# are left to set_locale, which handles e.g. regional variants.
|
|
384
|
+
report_options["trans"] = language
|
|
385
|
+
language = None
|
|
371
386
|
module = plugin_manager.load_plugin(report_data)
|
|
372
387
|
option_class = getattr(module, report_data.optionclass)
|
|
373
388
|
report_class = getattr(module, report_data.reportclass)
|
|
@@ -56,7 +56,7 @@ from .delete import delete_object, remove_deleted_from_search_indices
|
|
|
56
56
|
from .emit import GrampsJSONEncoder
|
|
57
57
|
from .filters import apply_filter
|
|
58
58
|
from .match import match_dates
|
|
59
|
-
from .sort import sort_objects
|
|
59
|
+
from .sort import sort_handles_by_raw_data, sort_objects
|
|
60
60
|
from .util import (
|
|
61
61
|
abort_with_message,
|
|
62
62
|
add_object,
|
|
@@ -614,21 +614,66 @@ class GrampsObjectsResource(GrampsObjectResourceHelper, Resource):
|
|
|
614
614
|
pass
|
|
615
615
|
return self.response(200, objects, args, total_items=len(objects))
|
|
616
616
|
|
|
617
|
+
query_method = self.db_handle.method("get_%s_handles", self.gramps_class_name)
|
|
618
|
+
assert query_method is not None # type checker
|
|
619
|
+
# for all objects except events, repos, and notes, Gramps supports
|
|
620
|
+
# a database-backed default sort order. Use that if no sort order
|
|
621
|
+
# requested.
|
|
622
|
+
sort_by_default = self.gramps_class_name not in ["Event", "Repository", "Note"]
|
|
623
|
+
|
|
624
|
+
# if a single page is requested and no filtering is needed, try to sort
|
|
625
|
+
# the handles without loading all objects to memory
|
|
626
|
+
if args["page"] > 0 and not (
|
|
627
|
+
"filter" in args
|
|
628
|
+
or "rules" in args
|
|
629
|
+
or "gql" in args
|
|
630
|
+
or "oql" in args
|
|
631
|
+
or args["dates"]
|
|
632
|
+
or (self.gramps_class_name == "Media" and args.get("filemissing"))
|
|
633
|
+
):
|
|
634
|
+
if "sort" in args:
|
|
635
|
+
# the database-backed default sort order is expensive and
|
|
636
|
+
# not needed here since the requested sort order replaces it
|
|
637
|
+
sorted_handles = sort_handles_by_raw_data(
|
|
638
|
+
self.db_handle,
|
|
639
|
+
self.gramps_class_name,
|
|
640
|
+
query_method(),
|
|
641
|
+
args["sort"],
|
|
642
|
+
locale=locale,
|
|
643
|
+
)
|
|
644
|
+
elif sort_by_default:
|
|
645
|
+
sorted_handles = query_method(sort_handles=True, locale=locale)
|
|
646
|
+
else:
|
|
647
|
+
sorted_handles = query_method()
|
|
648
|
+
if sorted_handles is not None:
|
|
649
|
+
offset = (args["page"] - 1) * args["pagesize"]
|
|
650
|
+
page_handles = sorted_handles[offset : offset + args["pagesize"]]
|
|
651
|
+
objects = []
|
|
652
|
+
for handle in page_handles:
|
|
653
|
+
try:
|
|
654
|
+
obj = self.get_object_from_handle(handle)
|
|
655
|
+
except HandleError:
|
|
656
|
+
# object was deleted after the handles were fetched
|
|
657
|
+
continue
|
|
658
|
+
if obj is None:
|
|
659
|
+
# object is no longer included by a proxy database
|
|
660
|
+
continue
|
|
661
|
+
objects.append(self.full_object(obj, args, locale=locale))
|
|
662
|
+
return self.response(
|
|
663
|
+
200, objects, args, total_items=len(sorted_handles)
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
if sort_by_default:
|
|
667
|
+
handles = query_method(sort_handles=True, locale=locale)
|
|
668
|
+
else:
|
|
669
|
+
handles = query_method()
|
|
670
|
+
|
|
617
671
|
# load all objects to memory
|
|
618
672
|
objects_name = GRAMPS_OBJECT_PLURAL[self.gramps_class_name]
|
|
619
673
|
iter_objects_method = self.db_handle.method("iter_%s", objects_name)
|
|
620
674
|
assert iter_objects_method is not None # type checker
|
|
621
675
|
objects = list(iter_objects_method())
|
|
622
676
|
|
|
623
|
-
# for all objects except events, repos, and notes, Gramps supports
|
|
624
|
-
# a database-backed default sort order. Use that if no sort order
|
|
625
|
-
# requested.
|
|
626
|
-
query_method = self.db_handle.method("get_%s_handles", self.gramps_class_name)
|
|
627
|
-
assert query_method is not None # type checker
|
|
628
|
-
if self.gramps_class_name in ["Event", "Repository", "Note"]:
|
|
629
|
-
handles = query_method()
|
|
630
|
-
else:
|
|
631
|
-
handles = query_method(sort_handles=True, locale=locale)
|
|
632
677
|
handle_index = {handle: index for index, handle in enumerate(handles)}
|
|
633
678
|
# sort objects by the sorted handle order
|
|
634
679
|
objects = sorted(
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import inspect
|
|
24
24
|
import json
|
|
25
|
+
import re
|
|
25
26
|
from collections import defaultdict
|
|
26
27
|
from collections.abc import Callable
|
|
27
28
|
from typing import Any
|
|
@@ -29,6 +30,7 @@ from typing import Any
|
|
|
29
30
|
import gramps.gen.filters as filters
|
|
30
31
|
from flask import Response, abort, current_app
|
|
31
32
|
from gramps.gen.db.base import DbReadBase
|
|
33
|
+
from gramps.gen.errors import FilterError
|
|
32
34
|
from gramps.gen.filters import GenericFilter
|
|
33
35
|
from gramps.gen.filters.rules import Rule
|
|
34
36
|
from gramps.gen.lib import Media, Person
|
|
@@ -99,6 +101,23 @@ _ADDITIONAL_RULES: dict[str, list[type[Rule]]] = {
|
|
|
99
101
|
"Media": [IsReferencedByObjectType],
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
# Rules shipped by Gramps that fail on every object of their namespace
|
|
105
|
+
_EXCLUDED_RULES: dict[str, set[str]] = {
|
|
106
|
+
"Repository": {"HasAttribute"}, # Repository has no attribute_list
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Errors raised by Gramps rules when evaluated with invalid values
|
|
110
|
+
# (e.g. unparsable dates or integers, invalid regular expressions)
|
|
111
|
+
_RULE_VALUE_ERRORS = (
|
|
112
|
+
FilterError,
|
|
113
|
+
ValueError,
|
|
114
|
+
TypeError,
|
|
115
|
+
AttributeError,
|
|
116
|
+
IndexError,
|
|
117
|
+
KeyError,
|
|
118
|
+
re.error,
|
|
119
|
+
)
|
|
120
|
+
|
|
102
121
|
_RULE_CLASS_CACHE: dict[str, dict[str, type[Rule]]] = {}
|
|
103
122
|
|
|
104
123
|
# Maps (parent_namespace, sub_namespace) to a function (db, obj) -> [sub_handle, ...]
|
|
@@ -130,7 +149,7 @@ def get_rule_map(namespace: str) -> dict[str, type[Rule]]:
|
|
|
130
149
|
if mod is None:
|
|
131
150
|
_RULE_CLASS_CACHE[namespace] = {}
|
|
132
151
|
else:
|
|
133
|
-
seen: set[str] = set()
|
|
152
|
+
seen: set[str] = set(_EXCLUDED_RULES.get(namespace, set()))
|
|
134
153
|
result: dict[str, type[Rule]] = {}
|
|
135
154
|
for _, cls in inspect.getmembers(mod, inspect.isclass):
|
|
136
155
|
if (
|
|
@@ -211,16 +230,61 @@ def get_custom_filters(args: dict[str, Any], namespace: str) -> list[dict[str, A
|
|
|
211
230
|
return filter_list
|
|
212
231
|
|
|
213
232
|
|
|
214
|
-
def
|
|
215
|
-
|
|
216
|
-
|
|
233
|
+
def _validate_rule_parms(
|
|
234
|
+
rule_parms: dict[str, Any], namespace: str
|
|
235
|
+
) -> tuple[type[Rule], list[str]]:
|
|
236
|
+
"""Return the rule class and its values as strings, aborting on invalid input.
|
|
237
|
+
|
|
238
|
+
Gramps rules expect string values, one per label. The number of values is not
|
|
239
|
+
enforced, as existing clients rely on both directions. Like Gramps when loading
|
|
240
|
+
saved filters, extra values are ignored. Missing values are passed through:
|
|
241
|
+
some rules supply defaults (e.g. HasNote), the others fail during evaluation,
|
|
242
|
+
which returns 422.
|
|
243
|
+
"""
|
|
244
|
+
name = rule_parms["name"]
|
|
245
|
+
rule_class = get_rule_map(namespace).get(name)
|
|
217
246
|
if rule_class is None:
|
|
218
247
|
abort(404)
|
|
219
248
|
assert rule_class is not None
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
249
|
+
values = rule_parms.get("values", [])
|
|
250
|
+
str_values = []
|
|
251
|
+
for value in values:
|
|
252
|
+
if isinstance(value, bool):
|
|
253
|
+
str_values.append("1" if value else "0")
|
|
254
|
+
elif isinstance(value, (str, int, float)):
|
|
255
|
+
str_values.append(str(value))
|
|
256
|
+
else:
|
|
257
|
+
abort_with_message(
|
|
258
|
+
422, f"Rule {name} values must be strings, numbers or booleans"
|
|
259
|
+
)
|
|
260
|
+
return rule_class, str_values[: len(rule_class.labels)]
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _validate_filter_parms(
|
|
264
|
+
filter_parms: dict[str, Any], namespace: str, depth: int = 0
|
|
265
|
+
) -> None:
|
|
266
|
+
"""Validate a (possibly nested) filter spec before evaluating it."""
|
|
267
|
+
if depth >= MAX_FILTER_DEPTH:
|
|
268
|
+
abort_with_message(400, "Filter nesting depth exceeded")
|
|
269
|
+
for item in filter_parms["rules"]:
|
|
270
|
+
if "name" in item:
|
|
271
|
+
_validate_rule_parms(item, namespace)
|
|
272
|
+
continue
|
|
273
|
+
sub_namespace = item.get("namespace", namespace)
|
|
274
|
+
if (
|
|
275
|
+
sub_namespace != namespace
|
|
276
|
+
and (namespace, sub_namespace) not in _NAMESPACE_BRIDGES
|
|
277
|
+
):
|
|
278
|
+
abort_with_message(
|
|
279
|
+
400, f"Unsupported namespace bridge: {namespace} → {sub_namespace}"
|
|
280
|
+
)
|
|
281
|
+
_validate_filter_parms(item, sub_namespace, depth + 1)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _build_rule_instance(rule_parms: dict[str, Any], namespace: str) -> Rule:
|
|
285
|
+
"""Instantiate a single rule from its parameter dict."""
|
|
286
|
+
rule_class, values = _validate_rule_parms(rule_parms, namespace)
|
|
287
|
+
return rule_class(values, use_regex=rule_parms.get("regex", False))
|
|
224
288
|
|
|
225
289
|
|
|
226
290
|
def _apply_filter_parms(
|
|
@@ -243,7 +307,12 @@ def _apply_filter_parms(
|
|
|
243
307
|
if "name" in item:
|
|
244
308
|
single = filters.GenericFilterFactory(namespace)()
|
|
245
309
|
single.add_rule(_build_rule_instance(item, namespace))
|
|
246
|
-
|
|
310
|
+
try:
|
|
311
|
+
result_sets.append(set(single.apply(db_handle, id_list=handles)))
|
|
312
|
+
except _RULE_VALUE_ERRORS as exc:
|
|
313
|
+
abort_with_message(
|
|
314
|
+
422, f"Rule {item['name']} could not be evaluated: {exc}"
|
|
315
|
+
)
|
|
247
316
|
elif item.get("namespace", namespace) != namespace:
|
|
248
317
|
sub_namespace = item["namespace"]
|
|
249
318
|
bridge = _NAMESPACE_BRIDGES.get((namespace, sub_namespace))
|
|
@@ -316,7 +385,13 @@ def apply_filter(
|
|
|
316
385
|
if args.get("filter"):
|
|
317
386
|
for filter_class in filters.CustomFilters.get_filters(namespace):
|
|
318
387
|
if args["filter"] == filter_class.get_name():
|
|
319
|
-
|
|
388
|
+
try:
|
|
389
|
+
return filter_class.apply(db_handle, id_list=handles)
|
|
390
|
+
except _RULE_VALUE_ERRORS as exc:
|
|
391
|
+
abort_with_message(
|
|
392
|
+
422,
|
|
393
|
+
f"Filter {args['filter']} could not be evaluated: {exc}",
|
|
394
|
+
)
|
|
320
395
|
abort(404)
|
|
321
396
|
|
|
322
397
|
try:
|
|
@@ -325,6 +400,7 @@ def apply_filter(
|
|
|
325
400
|
abort_with_message(400, "Error decoding JSON")
|
|
326
401
|
except ValidationError:
|
|
327
402
|
abort_with_message(422, "Filter does not adhere to schema")
|
|
403
|
+
_validate_filter_parms(filter_parms, namespace)
|
|
328
404
|
|
|
329
405
|
if handles is None:
|
|
330
406
|
query_method = db_handle.method("get_%s_handles", namespace)
|
|
@@ -54,7 +54,11 @@ def get_max_upload_bytes(free_bytes: int) -> int:
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
def write_upload_to_file(file_path: str, max_bytes: int) -> None:
|
|
57
|
-
"""Stream the request body to a file, aborting if it exceeds `max_bytes`.
|
|
57
|
+
"""Stream the request body to a file, aborting if it exceeds `max_bytes`.
|
|
58
|
+
|
|
59
|
+
Also aborts if the body size does not match the declared Content-Length
|
|
60
|
+
(e.g. a truncated upload). The caller is responsible for removing the file.
|
|
61
|
+
"""
|
|
58
62
|
request_stream = request.stream
|
|
59
63
|
size = 0
|
|
60
64
|
with open(file_path, "w+b") as ftmp:
|
|
@@ -67,6 +71,11 @@ def write_upload_to_file(file_path: str, max_bytes: int) -> None:
|
|
|
67
71
|
if size > max_bytes:
|
|
68
72
|
abort_with_message(413, "Uploaded archive is too large")
|
|
69
73
|
ftmp.write(chunk)
|
|
74
|
+
expected = request.content_length
|
|
75
|
+
if expected is not None and size and size != expected:
|
|
76
|
+
abort_with_message(
|
|
77
|
+
400, f"Upload incomplete: received {size} of {expected} bytes"
|
|
78
|
+
)
|
|
70
79
|
|
|
71
80
|
|
|
72
81
|
class MediaUploadZipResource(ProtectedResource):
|