howler-api 4.0.0.dev847__tar.gz → 4.0.0.dev871__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.
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/PKG-INFO +1 -1
- howler_api-4.0.0.dev871/howler/api/v1/utils/etag.py +106 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v2/ingest.py +21 -4
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v2/search.py +8 -11
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/collection.py +1 -1
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/base.py +21 -10
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/search_service.py +4 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/compat.py +6 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/pyproject.toml +1 -1
- howler_api-4.0.0.dev847/howler/api/v1/utils/etag.py +0 -84
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/README.md +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/add_label.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/add_to_bundle.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/add_to_case.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/change_field.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/demote.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/example_plugin.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/prioritization.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/promote.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/remove_from_bundle.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/remove_label.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/actions/transition.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/base.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/socket.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/action.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/analytic.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/auth.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/clue.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/configs.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/dossier.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/help.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/hit.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/notebook.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/overview.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/search.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/template.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/tool.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/user.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/utils/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v1/view.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v2/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/api/v2/case.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/app.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/README.md +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/classification.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/classification.yml +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/exceptions.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/loader.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/logging/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/logging/audit.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/logging/format.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/net.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/net_static.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/random_user.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/common/swagger.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/config.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/cronjobs/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/cronjobs/correlation.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/cronjobs/retention.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/cronjobs/view_cleanup.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/README.md +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/bulk.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/constants.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/exceptions.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/howler_store.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/migrations/fix_process.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/operations.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/schemas.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/store.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/support/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/support/build.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/support/schemas.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/types.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/error.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/external/README.md +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/external/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/external/generate_mitre.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/external/generate_sigma_rules.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/external/generate_tlds.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/external/reindex_data.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/external/wipe_databases.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/gunicorn_config.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/healthz.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/helper/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/helper/azure.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/helper/discover.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/helper/hit.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/helper/oauth.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/helper/search.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/helper/workflow.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/helper/ws.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/README.md +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/charter.txt +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/constants.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/helper.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/howler_enum.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/mixins.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/action.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/analytic.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/assemblyline.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/aws.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/azure.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/case.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/cbs.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/clue.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/config.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/dossier.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/agent.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/autonomous_system.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/client.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/cloud.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/code_signature.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/container.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/dns.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/egress.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/elf.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/email.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/error.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/event.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/faas.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/file.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/geo.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/group.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/hash.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/host.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/http.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/ingress.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/interface.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/network.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/observer.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/organization.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/os.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/pe.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/process.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/registry.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/related.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/rule.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/server.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/threat.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/tls.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/url.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/user.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/user_agent.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/vulnerability.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/gcp.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/hit.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/howler_data.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/lead.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/localized_label.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/observable.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/overview.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/pivot.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/record.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/template.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/user.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/models/view.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/random_data.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/odm/randomizer.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/patched.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/plugins/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/plugins/config.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/README.md +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/counters.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/events.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/hash.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/lock.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/comms.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/multi.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/named.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/priority.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/set.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/remote/datatypes/user_quota_tracker.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/security/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/security/socket.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/security/utils.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/action_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/analytic_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/auth_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/bundle_compat_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/case_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/config_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/correlation_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/docs_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/dossier_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/event_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/hit_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/jwt_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/lucene_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/notebook_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/observable_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/overview_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/template_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/user_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/services/viewer_service.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/telemetry.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/__init__.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/annotations.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/chunk.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/constants.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/dict_utils.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/isotime.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/list_utils.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/lucene.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/path.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/socket_utils.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/str_utils.py +0 -0
- {howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/utils/uid.py +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""ETag utility module for handling HTTP ETags in Flask responses.
|
|
2
|
+
|
|
3
|
+
ETags (Entity Tags) are HTTP headers used for web cache validation and conditional requests.
|
|
4
|
+
They help optimize performance by allowing clients to cache responses and only fetch
|
|
5
|
+
new data when the resource has actually changed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import functools
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from flask import Response, request
|
|
12
|
+
|
|
13
|
+
from howler.api import not_modified
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_etag(getter=None, check_if_match=True):
|
|
17
|
+
"""Decorator to add ETag handling to a Flask response.
|
|
18
|
+
|
|
19
|
+
This decorator implements HTTP ETag functionality for API endpoints, enabling:
|
|
20
|
+
- Conditional requests using If-Match headers
|
|
21
|
+
- Cache validation to prevent unnecessary data transfers
|
|
22
|
+
- Version tracking for resources
|
|
23
|
+
|
|
24
|
+
When ``getter`` is provided, the decorator pre-fetches the object and its
|
|
25
|
+
version, injects ``server_version`` into kwargs, caches the object, and
|
|
26
|
+
supports ``If-Match`` conditional requests.
|
|
27
|
+
|
|
28
|
+
When ``getter`` is ``None``, the decorator only handles converting
|
|
29
|
+
``(Response, version)`` return tuples into a single Response with the
|
|
30
|
+
``ETag`` header set. This is useful for endpoints that manage their own
|
|
31
|
+
version retrieval (e.g. v2 endpoints).
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
getter: Optional function that retrieves the object and its version.
|
|
35
|
+
When None, the decorator only handles ETag header setting.
|
|
36
|
+
check_if_match (bool): Whether to check If-Match headers for conditional requests.
|
|
37
|
+
Only used when getter is provided.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Decorated function with ETag support
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def wrapper(f):
|
|
44
|
+
"""Inner wrapper function that applies ETag functionality to the decorated function."""
|
|
45
|
+
|
|
46
|
+
@functools.wraps(f)
|
|
47
|
+
def generate_etag(*args, **kwargs):
|
|
48
|
+
"""Generate and handle ETags for the HTTP response."""
|
|
49
|
+
if getter is not None:
|
|
50
|
+
# Retrieve the object and its version using the provided getter function
|
|
51
|
+
# The getter should return (object, version) tuple
|
|
52
|
+
obj, version = getter(
|
|
53
|
+
kwargs.get("id", kwargs.get("username", None)),
|
|
54
|
+
as_odm=True,
|
|
55
|
+
version=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Handle conditional requests with If-Match header
|
|
59
|
+
# If the client's version matches the current version and it's a GET request
|
|
60
|
+
# without metadata parameter, return 304 Not Modified to save bandwidth
|
|
61
|
+
if (
|
|
62
|
+
check_if_match
|
|
63
|
+
and "If-Match" in request.headers
|
|
64
|
+
and request.headers["If-Match"] == version
|
|
65
|
+
and request.method == "GET"
|
|
66
|
+
and "metadata" not in request.args
|
|
67
|
+
):
|
|
68
|
+
return not_modified()
|
|
69
|
+
|
|
70
|
+
# Extract the resource type from the API path and create a cache key
|
|
71
|
+
# e.g., "/api/v1/users/123" becomes "cached_users"
|
|
72
|
+
key = re.sub(r"^\/api\/v\d+\/(\w+)\/.+$", r"cached_\1", request.path)
|
|
73
|
+
kwargs[key] = obj
|
|
74
|
+
|
|
75
|
+
# Call the original function with the cached object and version
|
|
76
|
+
values = f(*args, server_version=version, **kwargs)
|
|
77
|
+
|
|
78
|
+
# Handle different return value formats from the decorated function
|
|
79
|
+
# If there is only one return, it's just the response
|
|
80
|
+
if isinstance(values, Response):
|
|
81
|
+
# Only add ETag header for successful responses (not 409 Conflict or 400 Bad Request)
|
|
82
|
+
if values.status_code != 409 and values.status_code != 400:
|
|
83
|
+
values.headers["ETag"] = version
|
|
84
|
+
return values
|
|
85
|
+
|
|
86
|
+
# If there are two returns, it's the response and the new version
|
|
87
|
+
# This happens when the function modifies the resource and returns an updated version
|
|
88
|
+
else:
|
|
89
|
+
if values[0].status_code != 409 and values[0].status_code != 400:
|
|
90
|
+
# Add the new ETag version to successful responses
|
|
91
|
+
values[0].headers["ETag"] = values[1]
|
|
92
|
+
return values[0]
|
|
93
|
+
|
|
94
|
+
# No getter: just call the function and handle (Response, version) tuples
|
|
95
|
+
values = f(*args, **kwargs)
|
|
96
|
+
|
|
97
|
+
if isinstance(values, Response):
|
|
98
|
+
return values
|
|
99
|
+
|
|
100
|
+
if values[0].status_code != 409 and values[0].status_code != 400:
|
|
101
|
+
values[0].headers["ETag"] = values[1]
|
|
102
|
+
return values[0]
|
|
103
|
+
|
|
104
|
+
return generate_etag
|
|
105
|
+
|
|
106
|
+
return wrapper
|
|
@@ -5,6 +5,7 @@ from flask import request
|
|
|
5
5
|
from mergedeep import Strategy, merge
|
|
6
6
|
|
|
7
7
|
from howler.api import bad_request, created, forbidden, internal_error, make_subapi_blueprint, no_content, not_found, ok
|
|
8
|
+
from howler.api.v1.utils.etag import add_etag
|
|
8
9
|
from howler.common.exceptions import HowlerException, HowlerValueError
|
|
9
10
|
from howler.common.loader import datastore
|
|
10
11
|
from howler.common.logging import get_logger
|
|
@@ -162,8 +163,23 @@ def delete(indexes: str, user: User, **kwargs):
|
|
|
162
163
|
if non_existing_hit_ids := [id for id in ids if all(not ds[index].exists(id) for index in index_list)]:
|
|
163
164
|
return not_found(err=f"Record ids [{','.join(non_existing_hit_ids)}] do not exist.")
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
try:
|
|
167
|
+
remaining = set(ids)
|
|
168
|
+
for index in index_list:
|
|
169
|
+
if not remaining:
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
existing = [record_id for record_id in remaining if ds[index].exists(record_id)]
|
|
173
|
+
if not existing:
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
for record_id in existing:
|
|
177
|
+
ds[index].delete(record_id)
|
|
178
|
+
|
|
179
|
+
remaining -= set(existing)
|
|
180
|
+
ds[index].commit()
|
|
181
|
+
except DataStoreException as e:
|
|
182
|
+
return internal_error(err=str(e))
|
|
167
183
|
|
|
168
184
|
return no_content()
|
|
169
185
|
|
|
@@ -239,7 +255,8 @@ def validate(index: str, **kwargs):
|
|
|
239
255
|
@generate_swagger_docs()
|
|
240
256
|
@ingest_api.route("/<index>/<id>/overwrite", methods=["PATCH"])
|
|
241
257
|
@api_login(audit=False, required_priv=["W"])
|
|
242
|
-
|
|
258
|
+
@add_etag()
|
|
259
|
+
def overwrite(index: str, id: str, **kwargs):
|
|
243
260
|
"""Overwrite a record.
|
|
244
261
|
|
|
245
262
|
Variables:
|
|
@@ -286,7 +303,7 @@ def overwrite(index: str, id: str, server_version: str, **kwargs):
|
|
|
286
303
|
),
|
|
287
304
|
)
|
|
288
305
|
|
|
289
|
-
ds[index].save(id, odm(new_record) if odm else new_record, version=server_version)
|
|
306
|
+
ds[index].save(id, odm(new_record) if odm else new_record, version=kwargs.get("server_version"))
|
|
290
307
|
|
|
291
308
|
new_record, new_version = ds[index].get(id, as_obj=False, version=True)
|
|
292
309
|
|
|
@@ -5,7 +5,6 @@ from typing import Any
|
|
|
5
5
|
from elasticsearch import BadRequestError
|
|
6
6
|
from elasticsearch._sync.client.indices import IndicesClient
|
|
7
7
|
from flask import Request, request
|
|
8
|
-
from werkzeug.exceptions import BadRequest
|
|
9
8
|
|
|
10
9
|
from howler.api import bad_request, make_subapi_blueprint, ok
|
|
11
10
|
from howler.common.loader import datastore
|
|
@@ -30,10 +29,7 @@ def generate_params(request: Request, fields: list[str], multi_fields: list[str]
|
|
|
30
29
|
params = {}
|
|
31
30
|
|
|
32
31
|
if request.method == "POST":
|
|
33
|
-
|
|
34
|
-
req_data = request.json
|
|
35
|
-
except BadRequest:
|
|
36
|
-
req_data = {"query": "*:*"}
|
|
32
|
+
req_data = request.get_json(silent=True) or {"query": "*:*"}
|
|
37
33
|
|
|
38
34
|
params = {
|
|
39
35
|
**params,
|
|
@@ -131,7 +127,8 @@ def search(indexes: str, **kwargs):
|
|
|
131
127
|
return bad_request(err="There was no search query.")
|
|
132
128
|
|
|
133
129
|
metadata = params.pop("metadata", [])
|
|
134
|
-
|
|
130
|
+
access_control = params.pop("access_control", None)
|
|
131
|
+
result = search_service.search(indexes, query, access_control=access_control, **params)
|
|
135
132
|
|
|
136
133
|
if metadata and any(idx in index_list for idx in ["hit"]):
|
|
137
134
|
hit_service.augment_metadata(result["items"], metadata, user)
|
|
@@ -236,7 +233,7 @@ def count(index, **kwargs):
|
|
|
236
233
|
|
|
237
234
|
Result Example:
|
|
238
235
|
{
|
|
239
|
-
"
|
|
236
|
+
"count": 201, # Total results found
|
|
240
237
|
}
|
|
241
238
|
"""
|
|
242
239
|
user = kwargs["user"]
|
|
@@ -245,7 +242,7 @@ def count(index, **kwargs):
|
|
|
245
242
|
if collection is None:
|
|
246
243
|
return bad_request(err=f"Not a valid index to search in: {index}")
|
|
247
244
|
|
|
248
|
-
params, req_data = generate_params(request, [], [])
|
|
245
|
+
params, req_data = generate_params(request, ["timeout"], ["filters"])
|
|
249
246
|
|
|
250
247
|
boolean_fields = ["use_archive"]
|
|
251
248
|
params.update(
|
|
@@ -256,15 +253,15 @@ def count(index, **kwargs):
|
|
|
256
253
|
}
|
|
257
254
|
)
|
|
258
255
|
|
|
259
|
-
if has_access_control(index)
|
|
260
|
-
params.update({"access_control": user["access_control"]})
|
|
256
|
+
access_control = user["access_control"] if has_access_control(index) else None
|
|
261
257
|
|
|
262
258
|
query = req_data.get("query", None)
|
|
263
259
|
if not query:
|
|
264
260
|
return bad_request(err="There was no search query.")
|
|
265
261
|
|
|
262
|
+
filters = params.pop("filters", [])
|
|
266
263
|
try:
|
|
267
|
-
return ok(collection().count(query,
|
|
264
|
+
return ok(collection().count(query, filters, access_control=access_control))
|
|
268
265
|
except (SearchException, BadRequestError) as e:
|
|
269
266
|
return bad_request(err=f"SearchException: {e}")
|
|
270
267
|
|
|
@@ -1922,7 +1922,7 @@ class ESCollection(Generic[ModelType]):
|
|
|
1922
1922
|
search result object that consists of the following:
|
|
1923
1923
|
|
|
1924
1924
|
{
|
|
1925
|
-
"
|
|
1925
|
+
"count": 123456, # Total number of documents matching the query
|
|
1926
1926
|
}
|
|
1927
1927
|
|
|
1928
1928
|
:param query: lucene query to search for
|
|
@@ -1081,10 +1081,10 @@ class Model:
|
|
|
1081
1081
|
Args:
|
|
1082
1082
|
skip_mappings (bool): Skip over mappings where the real subfield names are unknown.
|
|
1083
1083
|
"""
|
|
1084
|
-
if skip_mappings and
|
|
1084
|
+
if not no_cache and skip_mappings and "_odm_field_cache_skip" in cls.__dict__:
|
|
1085
1085
|
return cls._odm_field_cache_skip
|
|
1086
1086
|
|
|
1087
|
-
if not skip_mappings and
|
|
1087
|
+
if not no_cache and not skip_mappings and "_odm_field_cache" in cls.__dict__:
|
|
1088
1088
|
return cls._odm_field_cache
|
|
1089
1089
|
|
|
1090
1090
|
out = dict()
|
|
@@ -1113,10 +1113,10 @@ class Model:
|
|
|
1113
1113
|
def add_namespace(cls, namespace: str, field: _Field, index=None, store=None, description=None):
|
|
1114
1114
|
recursive_set_name(field, namespace)
|
|
1115
1115
|
|
|
1116
|
-
if
|
|
1116
|
+
if "_odm_field_cache_skip" in cls.__dict__:
|
|
1117
1117
|
cls._odm_field_cache_skip[namespace.rstrip("_")] = field
|
|
1118
1118
|
|
|
1119
|
-
if
|
|
1119
|
+
if "_odm_field_cache" in cls.__dict__:
|
|
1120
1120
|
cls._odm_field_cache[namespace.rstrip("_")] = field
|
|
1121
1121
|
|
|
1122
1122
|
setattr(cls, namespace, field)
|
|
@@ -1131,10 +1131,10 @@ class Model:
|
|
|
1131
1131
|
|
|
1132
1132
|
@classmethod
|
|
1133
1133
|
def remove_namespace(cls, namespace: str):
|
|
1134
|
-
if
|
|
1134
|
+
if "_odm_field_cache_skip" in cls.__dict__:
|
|
1135
1135
|
del cls._odm_field_cache_skip[namespace.rstrip("_")]
|
|
1136
1136
|
|
|
1137
|
-
if
|
|
1137
|
+
if "_odm_field_cache" in cls.__dict__:
|
|
1138
1138
|
del cls._odm_field_cache[namespace.rstrip("_")]
|
|
1139
1139
|
|
|
1140
1140
|
delattr(cls, namespace)
|
|
@@ -1212,7 +1212,7 @@ class Model:
|
|
|
1212
1212
|
include_autogen_note=True,
|
|
1213
1213
|
defaults=None,
|
|
1214
1214
|
url_prefix="/howler/odm/class/",
|
|
1215
|
-
) ->
|
|
1215
|
+
) -> str:
|
|
1216
1216
|
markdown_content = (
|
|
1217
1217
|
(
|
|
1218
1218
|
'??? success "Auto-Generated Documentation"\n '
|
|
@@ -1524,12 +1524,23 @@ def model(index=None, store=None, description=None, id_field=None):
|
|
|
1524
1524
|
|
|
1525
1525
|
def _finish_model(cls):
|
|
1526
1526
|
cls._Model__description = description
|
|
1527
|
-
|
|
1527
|
+
fields = cls.fields()
|
|
1528
1528
|
|
|
1529
|
-
if
|
|
1529
|
+
if id_field is None:
|
|
1530
1530
|
cls._Model__id_field = f"{cls.__name__.lower()}_id"
|
|
1531
|
+
else:
|
|
1532
|
+
if not isinstance(id_field, str):
|
|
1533
|
+
raise HowlerTypeError(f"id_field must be a str, got {type(id_field).__name__}")
|
|
1534
|
+
|
|
1535
|
+
if not FLATTENED_OBJECT_SANITIZER.match(id_field) or id_field in BANNED_FIELDS:
|
|
1536
|
+
raise HowlerValueError(f"Illegal id_field name: {id_field}")
|
|
1537
|
+
|
|
1538
|
+
if id_field not in fields and id_field not in cls.flat_fields():
|
|
1539
|
+
raise HowlerValueError(f"id_field must reference a declared field: {id_field}")
|
|
1540
|
+
|
|
1541
|
+
cls._Model__id_field = id_field
|
|
1531
1542
|
|
|
1532
|
-
for name, field_data in
|
|
1543
|
+
for name, field_data in fields.items():
|
|
1533
1544
|
if not FIELD_SANITIZER.match(name) or name in BANNED_FIELDS:
|
|
1534
1545
|
raise HowlerValueError(f"Illegal variable name: {name}")
|
|
1535
1546
|
|
|
@@ -99,6 +99,7 @@ def search( # noqa: C901
|
|
|
99
99
|
timeout: int | None = None,
|
|
100
100
|
track_total_hits: bool = False,
|
|
101
101
|
metadata: list[str] | None = None,
|
|
102
|
+
access_control: str | None = None,
|
|
102
103
|
) -> SearchResult[dict[str, Any]]:
|
|
103
104
|
"""Search through specified index for a given query. Uses lucene search syntax for query.
|
|
104
105
|
|
|
@@ -136,6 +137,9 @@ def search( # noqa: C901
|
|
|
136
137
|
else:
|
|
137
138
|
parsed_filters = filters
|
|
138
139
|
|
|
140
|
+
if access_control:
|
|
141
|
+
parsed_filters.append(access_control)
|
|
142
|
+
|
|
139
143
|
if query is None:
|
|
140
144
|
query = "id:*"
|
|
141
145
|
|
|
@@ -14,6 +14,12 @@ else:
|
|
|
14
14
|
class StrEnum(str, _Enum): # type: ignore[no-redef]
|
|
15
15
|
"""str + Enum backport for Python < 3.11."""
|
|
16
16
|
|
|
17
|
+
def __str__(self) -> str:
|
|
18
|
+
return str.__str__(self)
|
|
19
|
+
|
|
20
|
+
def __format__(self, format_spec: str) -> str:
|
|
21
|
+
return str.__format__(self, format_spec)
|
|
22
|
+
|
|
17
23
|
# typing_extensions.TypedDict supports Generic[T] mixing on Python < 3.11;
|
|
18
24
|
# the stdlib version does not gain that until 3.11.
|
|
19
25
|
from typing_extensions import NotRequired, TypedDict # noqa: F401
|
|
@@ -152,7 +152,7 @@ suppress-none-returning = true
|
|
|
152
152
|
[tool.poetry]
|
|
153
153
|
package-mode = true
|
|
154
154
|
name = "howler-api"
|
|
155
|
-
version = "4.0.0.
|
|
155
|
+
version = "4.0.0.dev871"
|
|
156
156
|
description = "Howler - API server"
|
|
157
157
|
authors = [
|
|
158
158
|
"Canadian Centre for Cyber Security <howler@cyber.gc.ca>",
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""ETag utility module for handling HTTP ETags in Flask responses.
|
|
2
|
-
|
|
3
|
-
ETags (Entity Tags) are HTTP headers used for web cache validation and conditional requests.
|
|
4
|
-
They help optimize performance by allowing clients to cache responses and only fetch
|
|
5
|
-
new data when the resource has actually changed.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import functools
|
|
9
|
-
import re
|
|
10
|
-
|
|
11
|
-
from flask import Response, request
|
|
12
|
-
|
|
13
|
-
from howler.api import not_modified
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def add_etag(getter, check_if_match=True):
|
|
17
|
-
"""Decorator to add ETag handling to a Flask response.
|
|
18
|
-
|
|
19
|
-
This decorator implements HTTP ETag functionality for API endpoints, enabling:
|
|
20
|
-
- Conditional requests using If-Match headers
|
|
21
|
-
- Cache validation to prevent unnecessary data transfers
|
|
22
|
-
- Version tracking for resources
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
getter: Function that retrieves the object and its version
|
|
26
|
-
check_if_match (bool): Whether to check If-Match headers for conditional requests
|
|
27
|
-
|
|
28
|
-
Returns:
|
|
29
|
-
Decorated function with ETag support
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
def wrapper(f):
|
|
33
|
-
"""Inner wrapper function that applies ETag functionality to the decorated function."""
|
|
34
|
-
|
|
35
|
-
@functools.wraps(f)
|
|
36
|
-
def generate_etag(*args, **kwargs):
|
|
37
|
-
"""Generate and handle ETags for the HTTP response."""
|
|
38
|
-
# Retrieve the object and its version using the provided getter function
|
|
39
|
-
# The getter should return (object, version) tuple
|
|
40
|
-
obj, version = getter(
|
|
41
|
-
kwargs.get("id", kwargs.get("username", None)),
|
|
42
|
-
as_odm=True,
|
|
43
|
-
version=True,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
# Handle conditional requests with If-Match header
|
|
47
|
-
# If the client's version matches the current version and it's a GET request
|
|
48
|
-
# without metadata parameter, return 304 Not Modified to save bandwidth
|
|
49
|
-
if (
|
|
50
|
-
check_if_match
|
|
51
|
-
and "If-Match" in request.headers
|
|
52
|
-
and request.headers["If-Match"] == version
|
|
53
|
-
and request.method == "GET"
|
|
54
|
-
and "metadata" not in request.args
|
|
55
|
-
):
|
|
56
|
-
return not_modified()
|
|
57
|
-
|
|
58
|
-
# Extract the resource type from the API path and create a cache key
|
|
59
|
-
# e.g., "/api/v1/users/123" becomes "cached_users"
|
|
60
|
-
key = re.sub(r"^\/api\/v\d+\/(\w+)\/.+$", r"cached_\1", request.path)
|
|
61
|
-
kwargs[key] = obj
|
|
62
|
-
|
|
63
|
-
# Call the original function with the cached object and version
|
|
64
|
-
values = f(*args, server_version=version, **kwargs)
|
|
65
|
-
|
|
66
|
-
# Handle different return value formats from the decorated function
|
|
67
|
-
# If there is only one return, it's just the response
|
|
68
|
-
if isinstance(values, Response):
|
|
69
|
-
# Only add ETag header for successful responses (not 409 Conflict or 400 Bad Request)
|
|
70
|
-
if values.status_code != 409 and values.status_code != 400:
|
|
71
|
-
values.headers["ETag"] = version
|
|
72
|
-
return values
|
|
73
|
-
|
|
74
|
-
# If there are two returns, it's the response and the new version
|
|
75
|
-
# This happens when the function modifies the resource and returns an updated version
|
|
76
|
-
else:
|
|
77
|
-
if values[0].status_code != 409 and values[0].status_code != 400:
|
|
78
|
-
# Add the new ETag version to successful responses
|
|
79
|
-
values[0].headers["ETag"] = values[1]
|
|
80
|
-
return values[0]
|
|
81
|
-
|
|
82
|
-
return generate_etag
|
|
83
|
-
|
|
84
|
-
return wrapper
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_api-4.0.0.dev847 → howler_api-4.0.0.dev871}/howler/datastore/migrations/fix_process.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|