kinto 20.2.0__tar.gz → 20.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kinto might be problematic. Click here for more details.
- {kinto-20.2.0 → kinto-20.3.0}/PKG-INFO +1 -1
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/history.rst +18 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/memory.py +16 -3
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/__init__.py +1 -1
- kinto-20.3.0/kinto/core/storage/postgresql/migrations/migration_022_023.sql +5 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/schema.sql +3 -2
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/testing.py +8 -1
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/history/listener.py +60 -5
- {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/PKG-INFO +1 -1
- {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/SOURCES.txt +1 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_history.py +146 -0
- {kinto-20.2.0 → kinto-20.3.0}/.dockerignore +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/CODE_OF_CONDUCT.md +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/CONTRIBUTING.md +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/dependabot.yml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/release.yml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/workflows/labels.yaml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/workflows/publish.yml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/workflows/scheduled.yml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.github/workflows/test.yml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.gitignore +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/.readthedocs.yaml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/CHANGELOG.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/CONTRIBUTORS.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/Dockerfile +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/LICENSE +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/Makefile +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/README.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/SECURITY.md +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/SUPPORT.md +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/app.wsgi +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/constraints.in +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/constraints.txt +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docker-compose.yml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/_static/piwik.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/_static/theme_overrides.css +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/_templates/footer.html +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/_templates/indexcontent.html +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/_templates/layout.html +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-delete-list.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-delete-object.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-get-list.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-get-object.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-head-list.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-patch-object.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-post-list.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-put-object.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-delete-list.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-delete-object.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-get-list.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-get-object.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-patch-object.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-post-list.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-put-object.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/accounts.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/admin.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/authentication.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/backoff.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/batch.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/buckets.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/collections.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/deprecation.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/errors.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/filtering.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/flush.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/groups.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/index.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/openapi.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/openid.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/pagination.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/permissions.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/records.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/selecting_fields.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/sorting.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/timestamps.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/utilities.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/index.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/api/versioning.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/changelog.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/commandline.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/community.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/concepts.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/conf.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/configuration/good-practices.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/configuration/index.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/configuration/production.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/configuration/settings.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/_static/theme_overrides.css +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/api.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/cache.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/decorators.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/errors.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/glossary.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/images/cliquet-base.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/images/cliquet-mozilla.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/index.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/notifications.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/permission.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/quickstart.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/rationale.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/resource.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/storage.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/testing.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/utils.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/core/viewsets.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/faq.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/alwaysdata-button.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/architecture.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/color-formatter.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/concepts-general.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/concepts-permissions.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/heroku-button.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/kinto-logo.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/kinto-logo.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-admin.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-attachment.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-community.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-history.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-javascript.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-jsonschema.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-multiapps.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-permissions.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-python.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-selfhostable.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-synchronisation.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/overview-deployonce-selfhost.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/overview-features.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/overview-synchronisation.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/overview-use-cases.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/scalingo-button.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/screenshot-kinto-admin-1.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/screenshot-kinto-admin-2.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/screenshot-kinto-admin-3.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/screenshot-kinto-admin-4.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/sequence-storage.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/sync-both.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/sync-newest.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/images/sync-oldest.svg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/index.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/kinto-admin.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/overview.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/requirements.txt +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/troubleshooting.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/custom-id-generator.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/first-steps.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/index.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/install.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/notifications-custom.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/permission-setups.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/permissions.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/synchronisation.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/write-plugin.rst +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/__main__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/authorization.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/config/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/config/kinto.tpl +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/contribute.json +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/authentication.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/authorization.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/memcached.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/memory.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/postgresql/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/postgresql/schema.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/testing.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/cors.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/errors.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/pyramidhook.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/renderer.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/resource.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/service.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/util.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/validators/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/validators/_colander.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/validators/_marshmallow.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/converters/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/converters/exceptions.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/converters/parameters.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/converters/schema.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/swagger.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/templates/index.html +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/templates/index_script_template.html +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/util.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/views.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/decorators.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/errors.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/events.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/initialization.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/listeners/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/metrics.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/openapi.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/memory.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/postgresql/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/postgresql/migrations/migration_001_002.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/postgresql/schema.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/testing.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/resource/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/resource/model.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/resource/schema.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/resource/viewset.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/schema.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/scripts.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/statsd.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/exceptions.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/generators.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/client.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_001_002.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_002_003.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_003_004.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_004_005.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_005_006.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_006_007.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_007_008.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_008_009.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_009_010.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_010_011.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_011_012.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_012_013.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_013_014.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_014_015.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_015_016.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_016_017.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_017_018.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_018_019.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_019_020.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_020_021.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_021_022.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrator.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/pool.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/utils.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/testing.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/utils.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/batch.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/errors.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/heartbeat.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/hello.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/openapi.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/version.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/events.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/authentication.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/scripts.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/utils.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/views.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/README.md +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/VERSION +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/VERSION +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/asn1-EdZsLKOL.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/clojure-BMjYHr_A.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/css-BnMrqG3P.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/index-Cs7JVwIg.css +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/index-DwFOt8-z.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/javascript-qCveANmP.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/logo-VBRiKSPX.png +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/mllike-CXdrOF99.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/python-BuPzkPfP.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/rpm-CTu-6PCP.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/sql-D0XecflT.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/ttcn-cfg-B9xdYoR4.js +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/index.html +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/public/help.html +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/views.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/default_bucket/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/flush.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/history/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/history/views.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/openid/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/openid/utils.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/openid/views.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/prometheus.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/statsd.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/schema_validation.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/views/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/views/admin.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/views/buckets.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/views/collections.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/views/contribute.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/views/groups.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/views/permissions.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto/views/records.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/dependency_links.txt +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/entry_points.txt +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/requires.txt +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/top_level.txt +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/pyproject.toml +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/scripts/pull-kinto-admin.sh +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/setup.cfg +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/browser.ini +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/browser.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/listeners.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_base.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_cache_expires.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_events.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_filter.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_model.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_object.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_object_permissions.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_pagination.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_partial_response.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_preconditions.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_schema.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_sort.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_sync.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_views.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_views_cors.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_viewset.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/schema/postgresql-permission-1.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/schema/postgresql-storage-1.6.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/schema/postgresql-storage-11.sql +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/support.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_authentication.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_authorization.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_cache.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_decorators.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_deprecation.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_errors.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_initialization.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_listeners.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_logging.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_metrics.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_openapi.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_permission.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_schema.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_scripts.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_storage.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_storage_migrations.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_storage_pool.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_utils.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_batch.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_errors.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_heartbeat.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_hello.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_openapi.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_postgresql.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_transaction.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/testapp/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/testapp/static/index.html +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/testapp/views.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/core/testplugin/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/functional.ini +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/functional.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/support.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_plugins.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_resources.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_buckets.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_collections.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_errors.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_groups.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_records.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_validation.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/__init__.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_accounts.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_admin.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_default_bucket.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_flush.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_openid.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_prometheus.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_statsd.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/support.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_authorization.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_config.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_configuration/test.ini +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_init.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_main.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_admin.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_buckets.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_collections.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_collections_cache.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_contribute.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_disable_default.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_groups.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_hello.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_metrics.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_objects_permissions.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_permissions.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_records.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_schema_collection.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_schema_group.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_schema_record.py +0 -0
- {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_version.py +0 -0
|
@@ -195,3 +195,21 @@ It is possible to exclude certain resources from being tracked by history using
|
|
|
195
195
|
|
|
196
196
|
kinto.history.exclude_resources = /buckets/preview
|
|
197
197
|
/buckets/signed/collections/certificates
|
|
198
|
+
|
|
199
|
+
Or certain users:
|
|
200
|
+
|
|
201
|
+
.. code-block:: ini
|
|
202
|
+
|
|
203
|
+
kinto.history.exclude_user_ids = account:quicksuggest
|
|
204
|
+
|
|
205
|
+
In order to limit the size of the history length of each resource:
|
|
206
|
+
|
|
207
|
+
.. code-block:: ini
|
|
208
|
+
|
|
209
|
+
kinto.history.auto_trim_max_count = 100
|
|
210
|
+
|
|
211
|
+
In order to limit the size of the history length of each resource only for certain users:
|
|
212
|
+
|
|
213
|
+
.. code-block:: ini
|
|
214
|
+
|
|
215
|
+
kinto.history.auto_trim_user_ids = account:quicksuggest
|
|
@@ -288,13 +288,26 @@ class Storage(MemoryBasedStorage):
|
|
|
288
288
|
modified_field=DEFAULT_MODIFIED_FIELD,
|
|
289
289
|
):
|
|
290
290
|
parent_id_match = re.compile(parent_id.replace("*", ".*"))
|
|
291
|
-
|
|
291
|
+
|
|
292
|
+
timestamps_by_parent_id = {
|
|
292
293
|
pid: resources
|
|
293
|
-
for pid, resources in self.
|
|
294
|
+
for pid, resources in self._timestamps.items()
|
|
294
295
|
if parent_id_match.match(pid)
|
|
295
296
|
}
|
|
297
|
+
if resource_name is not None:
|
|
298
|
+
for pid, resources in timestamps_by_parent_id.items():
|
|
299
|
+
del self._timestamps[pid][resource_name]
|
|
300
|
+
else:
|
|
301
|
+
for pid, resources in timestamps_by_parent_id.items():
|
|
302
|
+
del self._timestamps[pid]
|
|
303
|
+
|
|
296
304
|
num_deleted = 0
|
|
297
|
-
|
|
305
|
+
tombstones_by_parent_id = {
|
|
306
|
+
pid: resources
|
|
307
|
+
for pid, resources in self._cemetery.items()
|
|
308
|
+
if parent_id_match.match(pid)
|
|
309
|
+
}
|
|
310
|
+
for pid, resources in tombstones_by_parent_id.items():
|
|
298
311
|
if resource_name is not None:
|
|
299
312
|
resources = {resource_name: resources[resource_name]}
|
|
300
313
|
for resource, resource_objects in resources.items():
|
|
@@ -47,7 +47,8 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_objects_parent_id_resource_name_last_modif
|
|
|
47
47
|
ON objects(parent_id, resource_name, last_modified DESC);
|
|
48
48
|
CREATE INDEX IF NOT EXISTS idx_objects_last_modified_epoch
|
|
49
49
|
ON objects(as_epoch(last_modified));
|
|
50
|
-
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_objects_resource_name_parent_id_deleted
|
|
51
|
+
ON objects(resource_name, parent_id, deleted);
|
|
51
52
|
|
|
52
53
|
CREATE TABLE IF NOT EXISTS timestamps (
|
|
53
54
|
parent_id TEXT NOT NULL COLLATE "C",
|
|
@@ -131,4 +132,4 @@ INSERT INTO metadata (name, value) VALUES ('created_at', NOW()::TEXT);
|
|
|
131
132
|
|
|
132
133
|
-- Set storage schema version.
|
|
133
134
|
-- Should match ``kinto.core.storage.postgresql.PostgreSQL.schema_version``
|
|
134
|
-
INSERT INTO metadata (name, value) VALUES ('storage_schema_version', '
|
|
135
|
+
INSERT INTO metadata (name, value) VALUES ('storage_schema_version', '23');
|
|
@@ -1296,6 +1296,9 @@ class DeletedObjectsTest:
|
|
|
1296
1296
|
self.create_object(parent_id="/abc/a", resource_name="c")
|
|
1297
1297
|
self.create_object(parent_id="/efg", resource_name="c")
|
|
1298
1298
|
|
|
1299
|
+
all_timestamps = self.storage.all_resources_timestamps(resource_name="c")
|
|
1300
|
+
self.assertEqual(set(all_timestamps.keys()), {"/abc/a", "/efg"})
|
|
1301
|
+
|
|
1299
1302
|
before1 = self.storage.resource_timestamp(parent_id="/abc/a", resource_name="c")
|
|
1300
1303
|
# Different parent_id with object.
|
|
1301
1304
|
before2 = self.storage.resource_timestamp(parent_id="/efg", resource_name="c")
|
|
@@ -1305,11 +1308,15 @@ class DeletedObjectsTest:
|
|
|
1305
1308
|
self.storage.delete_all(parent_id="/abc/*", resource_name=None, with_deleted=False)
|
|
1306
1309
|
self.storage.purge_deleted(parent_id="/abc/*", resource_name=None)
|
|
1307
1310
|
|
|
1311
|
+
all_timestamps = self.storage.all_resources_timestamps(resource_name="c")
|
|
1312
|
+
self.assertEqual(set(all_timestamps.keys()), {"/efg", "/ijk"})
|
|
1313
|
+
|
|
1314
|
+
time.sleep(0.002) # make sure we don't recreate timestamps at same msec.
|
|
1308
1315
|
after1 = self.storage.resource_timestamp(parent_id="/abc/a", resource_name="c")
|
|
1309
1316
|
after2 = self.storage.resource_timestamp(parent_id="/efg", resource_name="c")
|
|
1310
1317
|
after3 = self.storage.resource_timestamp(parent_id="/ijk", resource_name="c")
|
|
1311
1318
|
|
|
1312
|
-
self.assertNotEqual(before1, after1)
|
|
1319
|
+
self.assertNotEqual(before1, after1) # timestamp was removed, it will differ.
|
|
1313
1320
|
self.assertEqual(before2, after2)
|
|
1314
1321
|
self.assertEqual(before3, after3)
|
|
1315
1322
|
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from datetime import datetime, timezone
|
|
2
3
|
|
|
3
4
|
from pyramid.settings import asbool, aslist
|
|
4
5
|
|
|
5
|
-
from kinto.core.
|
|
6
|
+
from kinto.core.storage import Filter, Sort
|
|
7
|
+
from kinto.core.utils import COMPARISON, instance_uri
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
6
11
|
|
|
7
12
|
|
|
8
13
|
def on_resource_changed(event):
|
|
@@ -14,15 +19,26 @@ def on_resource_changed(event):
|
|
|
14
19
|
payload = event.payload
|
|
15
20
|
resource_name = payload["resource_name"]
|
|
16
21
|
event_uri = payload["uri"]
|
|
17
|
-
|
|
18
|
-
bucket_id = None
|
|
19
|
-
bucket_uri = None
|
|
20
|
-
collection_uri = None
|
|
22
|
+
user_id = payload["user_id"]
|
|
21
23
|
|
|
22
24
|
storage = event.request.registry.storage
|
|
23
25
|
permission = event.request.registry.permission
|
|
24
26
|
settings = event.request.registry.settings
|
|
25
27
|
|
|
28
|
+
excluded_user_ids = aslist(settings.get("history.exclude_user_ids", ""))
|
|
29
|
+
if user_id in excluded_user_ids:
|
|
30
|
+
logger.info(f"History entries for user {user_id!r} are disabled in config")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
trim_history_max = int(settings.get("history.auto_trim_max_count", "-1"))
|
|
34
|
+
is_trim_enabled = trim_history_max > 0
|
|
35
|
+
trim_user_ids = aslist(settings.get("history.auto_trim_user_ids", ""))
|
|
36
|
+
is_trim_by_user_enabled = len(trim_user_ids) > 0
|
|
37
|
+
|
|
38
|
+
bucket_id = None
|
|
39
|
+
bucket_uri = None
|
|
40
|
+
collection_uri = None
|
|
41
|
+
|
|
26
42
|
excluded_resources = aslist(settings.get("history.exclude_resources", ""))
|
|
27
43
|
|
|
28
44
|
targets = []
|
|
@@ -38,6 +54,7 @@ def on_resource_changed(event):
|
|
|
38
54
|
bucket_uri = instance_uri(event.request, "bucket", id=bucket_id)
|
|
39
55
|
|
|
40
56
|
if bucket_uri in excluded_resources:
|
|
57
|
+
logger.info(f"History entries for bucket {bucket_uri!r} are disabled in config")
|
|
41
58
|
continue
|
|
42
59
|
|
|
43
60
|
if "collection_id" in payload:
|
|
@@ -46,6 +63,9 @@ def on_resource_changed(event):
|
|
|
46
63
|
event.request, "collection", bucket_id=bucket_id, id=collection_id
|
|
47
64
|
)
|
|
48
65
|
if collection_uri in excluded_resources:
|
|
66
|
+
logger.info(
|
|
67
|
+
f"History entries for collection {collection_uri!r} are disabled in config"
|
|
68
|
+
)
|
|
49
69
|
continue
|
|
50
70
|
|
|
51
71
|
# On POST .../records, the URI does not contain the newly created
|
|
@@ -59,6 +79,7 @@ def on_resource_changed(event):
|
|
|
59
79
|
uri = "/".join(parts)
|
|
60
80
|
|
|
61
81
|
if uri in excluded_resources:
|
|
82
|
+
logger.info(f"History entries for record {uri!r} are disabled in config")
|
|
62
83
|
continue
|
|
63
84
|
|
|
64
85
|
targets.append((uri, target))
|
|
@@ -109,6 +130,40 @@ def on_resource_changed(event):
|
|
|
109
130
|
# Note: this will be rolledback if the transaction is rolledback.
|
|
110
131
|
entry = storage.create(parent_id=bucket_uri, resource_name="history", obj=attrs)
|
|
111
132
|
|
|
133
|
+
# If enabled, we trim history by resource.
|
|
134
|
+
# This means that we will only keep the last `auto_trim_max_count` history entries
|
|
135
|
+
# for this same type of object (eg. `collection`, `record`).
|
|
136
|
+
#
|
|
137
|
+
# If trim by user is enabled, we only trim if the user matches the config
|
|
138
|
+
# and we only delete the history entries of this user.
|
|
139
|
+
# This means that if a user touches X different types of objects, we will keep
|
|
140
|
+
# ``(X * auto_trim_max_count)`` entries.
|
|
141
|
+
if is_trim_enabled and (not is_trim_by_user_enabled or user_id in trim_user_ids):
|
|
142
|
+
filters = [
|
|
143
|
+
Filter("resource_name", resource_name, COMPARISON.EQ),
|
|
144
|
+
]
|
|
145
|
+
if is_trim_by_user_enabled:
|
|
146
|
+
filters.append(Filter("user_id", user_id, COMPARISON.EQ))
|
|
147
|
+
|
|
148
|
+
# Identify the oldest entry to keep.
|
|
149
|
+
previous_entries = storage.list_all(
|
|
150
|
+
parent_id=bucket_uri,
|
|
151
|
+
resource_name="history",
|
|
152
|
+
filters=filters,
|
|
153
|
+
sorting=[Sort("last_modified", -1)],
|
|
154
|
+
limit=trim_history_max + 1,
|
|
155
|
+
)
|
|
156
|
+
# And delete all older ones.
|
|
157
|
+
if len(previous_entries) > trim_history_max:
|
|
158
|
+
trim_before_timestamp = previous_entries[-1]["last_modified"]
|
|
159
|
+
deleted = storage.delete_all(
|
|
160
|
+
parent_id=bucket_uri,
|
|
161
|
+
resource_name="history",
|
|
162
|
+
filters=filters
|
|
163
|
+
+ [Filter("last_modified", trim_before_timestamp, COMPARISON.MAX)],
|
|
164
|
+
)
|
|
165
|
+
logger.debug(f"Trimmed {len(deleted)} old history entries.")
|
|
166
|
+
|
|
112
167
|
# Without explicit permissions, the ACLs on the history entries will
|
|
113
168
|
# fully depend on the inherited permission tree (eg. bucket:read, bucket:write).
|
|
114
169
|
# This basically means that if user loose the permissions on the related
|
|
@@ -240,6 +240,7 @@ kinto/core/storage/postgresql/migrations/migration_018_019.sql
|
|
|
240
240
|
kinto/core/storage/postgresql/migrations/migration_019_020.sql
|
|
241
241
|
kinto/core/storage/postgresql/migrations/migration_020_021.sql
|
|
242
242
|
kinto/core/storage/postgresql/migrations/migration_021_022.sql
|
|
243
|
+
kinto/core/storage/postgresql/migrations/migration_022_023.sql
|
|
243
244
|
kinto/core/views/__init__.py
|
|
244
245
|
kinto/core/views/batch.py
|
|
245
246
|
kinto/core/views/errors.py
|
|
@@ -670,6 +670,152 @@ class ExcludeResourcesTest(HistoryWebTest):
|
|
|
670
670
|
assert len(entries) == 0 # nothing.
|
|
671
671
|
|
|
672
672
|
|
|
673
|
+
class ExcludeUserIdTest(HistoryWebTest):
|
|
674
|
+
joan_headers = get_user_headers("joan")
|
|
675
|
+
joan_principal = "basicauth:64942e918f6481e3101f3a22a87a3206480923ccc0d4387cff9cb1ae0af21217"
|
|
676
|
+
|
|
677
|
+
@classmethod
|
|
678
|
+
def get_app_settings(cls, extras=None):
|
|
679
|
+
settings = super().get_app_settings(extras)
|
|
680
|
+
settings["history.exclude_user_ids"] = cls.joan_principal
|
|
681
|
+
return settings
|
|
682
|
+
|
|
683
|
+
def setUp(self):
|
|
684
|
+
self.app.put_json("/buckets/bid", headers=self.headers)
|
|
685
|
+
self.app.put_json(
|
|
686
|
+
"/buckets/bid/collections/cid",
|
|
687
|
+
{"permissions": {"write": ["system.Authenticated"]}},
|
|
688
|
+
headers=self.headers,
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
def test_entries_exclude_joan(self):
|
|
692
|
+
resp = self.app.get("/buckets/bid/history", headers=self.headers)
|
|
693
|
+
entries = resp.json["data"]
|
|
694
|
+
self.assertEqual(len(entries), 2) # creations
|
|
695
|
+
|
|
696
|
+
self.app.post("/buckets/bid/collections/cid/records", headers=self.headers)
|
|
697
|
+
self.app.post("/buckets/bid/collections/cid/records", headers=self.joan_headers)
|
|
698
|
+
|
|
699
|
+
resp = self.app.get("/buckets/bid/history", headers=self.headers)
|
|
700
|
+
entries = resp.json["data"]
|
|
701
|
+
self.assertEqual(len(entries), 3) # not 4
|
|
702
|
+
self.assertEqual(set(e["user_id"] for e in entries), {self.principal})
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
class TrimHistoryTest(HistoryWebTest):
|
|
706
|
+
@classmethod
|
|
707
|
+
def get_app_settings(cls, extras=None):
|
|
708
|
+
settings = super().get_app_settings(extras)
|
|
709
|
+
settings["history.auto_trim_max_count"] = "5"
|
|
710
|
+
return settings
|
|
711
|
+
|
|
712
|
+
def test_history_length_is_limited_by_resource(self):
|
|
713
|
+
for i in range(10):
|
|
714
|
+
self.app.put_json("/buckets/bid", {"data": {"i": i}}, headers=self.headers)
|
|
715
|
+
self.app.put(f"/buckets/bid/collections/cid-{i}", headers=self.headers)
|
|
716
|
+
|
|
717
|
+
resp = self.app.get("/buckets/bid/history", headers=self.headers)
|
|
718
|
+
by_uri = {}
|
|
719
|
+
for entry in resp.json["data"]:
|
|
720
|
+
by_uri.setdefault(entry["uri"], []).append(entry)
|
|
721
|
+
|
|
722
|
+
self.assertEqual(
|
|
723
|
+
{
|
|
724
|
+
"/buckets/bid": 5,
|
|
725
|
+
"/buckets/bid/collections/cid-5": 1,
|
|
726
|
+
"/buckets/bid/collections/cid-6": 1,
|
|
727
|
+
"/buckets/bid/collections/cid-7": 1,
|
|
728
|
+
"/buckets/bid/collections/cid-8": 1,
|
|
729
|
+
"/buckets/bid/collections/cid-9": 1,
|
|
730
|
+
},
|
|
731
|
+
{uri: len(entries) for uri, entries in by_uri.items()},
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
class TrimUserIdTest(HistoryWebTest):
|
|
736
|
+
joan_headers = get_user_headers("joan")
|
|
737
|
+
joan_principal = "basicauth:64942e918f6481e3101f3a22a87a3206480923ccc0d4387cff9cb1ae0af21217"
|
|
738
|
+
|
|
739
|
+
@classmethod
|
|
740
|
+
def get_app_settings(cls, extras=None):
|
|
741
|
+
settings = super().get_app_settings(extras)
|
|
742
|
+
settings["history.auto_trim_user_ids"] = cls.joan_principal
|
|
743
|
+
settings["history.auto_trim_max_count"] = "5"
|
|
744
|
+
return settings
|
|
745
|
+
|
|
746
|
+
def setUp(self):
|
|
747
|
+
self.app.put_json(
|
|
748
|
+
"/buckets/bid",
|
|
749
|
+
{"permissions": {"write": ["system.Authenticated"]}},
|
|
750
|
+
headers=self.headers,
|
|
751
|
+
)
|
|
752
|
+
self.app.put_json(
|
|
753
|
+
"/buckets/bid/collections/cid",
|
|
754
|
+
headers=self.headers,
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
def test_unlisted_user_entries_are_not_limited(self):
|
|
758
|
+
for i in range(10):
|
|
759
|
+
self.app.put(f"/buckets/bid/groups/gid-{i}", headers=self.headers)
|
|
760
|
+
|
|
761
|
+
resp = self.app.get(f"/buckets/bid/history?user_id={self.principal}", headers=self.headers)
|
|
762
|
+
|
|
763
|
+
self.assertEqual(len(resp.json["data"]), 12) # bucket + collection + 10 groups
|
|
764
|
+
|
|
765
|
+
def test_listed_user_entries_are_limited(self):
|
|
766
|
+
for i in range(10):
|
|
767
|
+
self.app.put(f"/buckets/bid/groups/gid-{i}", headers=self.joan_headers)
|
|
768
|
+
|
|
769
|
+
resp = self.app.get(
|
|
770
|
+
f"/buckets/bid/history?user_id={self.joan_principal}", headers=self.joan_headers
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
self.assertEqual(len(resp.json["data"]), 5)
|
|
774
|
+
|
|
775
|
+
def test_trim_is_by_user_and_by_resource_name(self):
|
|
776
|
+
for headers in (self.headers, self.joan_headers):
|
|
777
|
+
for i in range(10):
|
|
778
|
+
self.app.patch_json(
|
|
779
|
+
"/buckets/bid/collections/cid", {"data": {"attr": i}}, headers=headers
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
for headers in (self.headers, self.joan_headers):
|
|
783
|
+
for i in range(3):
|
|
784
|
+
# Delete all records in it.
|
|
785
|
+
self.app.delete("/buckets/bid/collections/cid/records", headers=headers)
|
|
786
|
+
for j in range(10):
|
|
787
|
+
# Recreate new ones with different ids
|
|
788
|
+
self.app.put(
|
|
789
|
+
f"/buckets/bid/collections/cid/records/rid-{i * 10 + j}", headers=headers
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
resp = self.app.get("/buckets/bid/history", headers=self.headers)
|
|
793
|
+
|
|
794
|
+
by_user_id = {}
|
|
795
|
+
for entry in resp.json["data"]:
|
|
796
|
+
by_user_id.setdefault(entry["user_id"], {}).setdefault(entry["uri"], []).append(entry)
|
|
797
|
+
self.assertEqual(
|
|
798
|
+
{
|
|
799
|
+
"/buckets/bid/collections/cid": 5, # Out of 10 on the collection
|
|
800
|
+
"/buckets/bid/collections/cid/records/rid-29": 1,
|
|
801
|
+
"/buckets/bid/collections/cid/records/rid-28": 1,
|
|
802
|
+
"/buckets/bid/collections/cid/records/rid-27": 1,
|
|
803
|
+
"/buckets/bid/collections/cid/records/rid-26": 1,
|
|
804
|
+
"/buckets/bid/collections/cid/records/rid-25": 1,
|
|
805
|
+
},
|
|
806
|
+
{k: len(v) for k, v in by_user_id[self.joan_principal].items()},
|
|
807
|
+
)
|
|
808
|
+
# Other is not trimmed.
|
|
809
|
+
self.assertEqual(
|
|
810
|
+
max(len(by_uri) for by_uri in by_user_id[self.principal].values()),
|
|
811
|
+
11, # 1 creation + 10 patch
|
|
812
|
+
)
|
|
813
|
+
self.assertEqual(
|
|
814
|
+
len(by_user_id[self.principal].keys()),
|
|
815
|
+
32, # 1 bucket + 1 collection + 30 records
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
|
|
673
819
|
class DisabledExplicitPermissionsTest(HistoryWebTest, unittest.TestCase):
|
|
674
820
|
@classmethod
|
|
675
821
|
def get_app_settings(cls, extras=None):
|
|
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
|
|
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
|