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.

Files changed (394) hide show
  1. {kinto-20.2.0 → kinto-20.3.0}/PKG-INFO +1 -1
  2. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/history.rst +18 -0
  3. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/memory.py +16 -3
  4. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/__init__.py +1 -1
  5. kinto-20.3.0/kinto/core/storage/postgresql/migrations/migration_022_023.sql +5 -0
  6. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/schema.sql +3 -2
  7. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/testing.py +8 -1
  8. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/history/listener.py +60 -5
  9. {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/PKG-INFO +1 -1
  10. {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/SOURCES.txt +1 -0
  11. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_history.py +146 -0
  12. {kinto-20.2.0 → kinto-20.3.0}/.dockerignore +0 -0
  13. {kinto-20.2.0 → kinto-20.3.0}/.github/CODE_OF_CONDUCT.md +0 -0
  14. {kinto-20.2.0 → kinto-20.3.0}/.github/CONTRIBUTING.md +0 -0
  15. {kinto-20.2.0 → kinto-20.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  16. {kinto-20.2.0 → kinto-20.3.0}/.github/dependabot.yml +0 -0
  17. {kinto-20.2.0 → kinto-20.3.0}/.github/release.yml +0 -0
  18. {kinto-20.2.0 → kinto-20.3.0}/.github/workflows/labels.yaml +0 -0
  19. {kinto-20.2.0 → kinto-20.3.0}/.github/workflows/publish.yml +0 -0
  20. {kinto-20.2.0 → kinto-20.3.0}/.github/workflows/scheduled.yml +0 -0
  21. {kinto-20.2.0 → kinto-20.3.0}/.github/workflows/test.yml +0 -0
  22. {kinto-20.2.0 → kinto-20.3.0}/.gitignore +0 -0
  23. {kinto-20.2.0 → kinto-20.3.0}/.readthedocs.yaml +0 -0
  24. {kinto-20.2.0 → kinto-20.3.0}/CHANGELOG.rst +0 -0
  25. {kinto-20.2.0 → kinto-20.3.0}/CONTRIBUTORS.rst +0 -0
  26. {kinto-20.2.0 → kinto-20.3.0}/Dockerfile +0 -0
  27. {kinto-20.2.0 → kinto-20.3.0}/LICENSE +0 -0
  28. {kinto-20.2.0 → kinto-20.3.0}/Makefile +0 -0
  29. {kinto-20.2.0 → kinto-20.3.0}/README.rst +0 -0
  30. {kinto-20.2.0 → kinto-20.3.0}/SECURITY.md +0 -0
  31. {kinto-20.2.0 → kinto-20.3.0}/SUPPORT.md +0 -0
  32. {kinto-20.2.0 → kinto-20.3.0}/app.wsgi +0 -0
  33. {kinto-20.2.0 → kinto-20.3.0}/constraints.in +0 -0
  34. {kinto-20.2.0 → kinto-20.3.0}/constraints.txt +0 -0
  35. {kinto-20.2.0 → kinto-20.3.0}/docker-compose.yml +0 -0
  36. {kinto-20.2.0 → kinto-20.3.0}/docs/_static/piwik.js +0 -0
  37. {kinto-20.2.0 → kinto-20.3.0}/docs/_static/theme_overrides.css +0 -0
  38. {kinto-20.2.0 → kinto-20.3.0}/docs/_templates/footer.html +0 -0
  39. {kinto-20.2.0 → kinto-20.3.0}/docs/_templates/indexcontent.html +0 -0
  40. {kinto-20.2.0 → kinto-20.3.0}/docs/_templates/layout.html +0 -0
  41. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-delete-list.rst +0 -0
  42. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-delete-object.rst +0 -0
  43. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-get-list.rst +0 -0
  44. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-get-object.rst +0 -0
  45. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-head-list.rst +0 -0
  46. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-patch-object.rst +0 -0
  47. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-post-list.rst +0 -0
  48. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_details-put-object.rst +0 -0
  49. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-delete-list.rst +0 -0
  50. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-delete-object.rst +0 -0
  51. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-get-list.rst +0 -0
  52. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-get-object.rst +0 -0
  53. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-patch-object.rst +0 -0
  54. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-post-list.rst +0 -0
  55. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/_status-put-object.rst +0 -0
  56. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/accounts.rst +0 -0
  57. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/admin.rst +0 -0
  58. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/authentication.rst +0 -0
  59. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/backoff.rst +0 -0
  60. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/batch.rst +0 -0
  61. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/buckets.rst +0 -0
  62. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/collections.rst +0 -0
  63. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/deprecation.rst +0 -0
  64. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/errors.rst +0 -0
  65. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/filtering.rst +0 -0
  66. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/flush.rst +0 -0
  67. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/groups.rst +0 -0
  68. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/index.rst +0 -0
  69. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/openapi.rst +0 -0
  70. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/openid.rst +0 -0
  71. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/pagination.rst +0 -0
  72. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/permissions.rst +0 -0
  73. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/records.rst +0 -0
  74. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/selecting_fields.rst +0 -0
  75. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/sorting.rst +0 -0
  76. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/timestamps.rst +0 -0
  77. {kinto-20.2.0 → kinto-20.3.0}/docs/api/1.x/utilities.rst +0 -0
  78. {kinto-20.2.0 → kinto-20.3.0}/docs/api/index.rst +0 -0
  79. {kinto-20.2.0 → kinto-20.3.0}/docs/api/versioning.rst +0 -0
  80. {kinto-20.2.0 → kinto-20.3.0}/docs/changelog.rst +0 -0
  81. {kinto-20.2.0 → kinto-20.3.0}/docs/commandline.rst +0 -0
  82. {kinto-20.2.0 → kinto-20.3.0}/docs/community.rst +0 -0
  83. {kinto-20.2.0 → kinto-20.3.0}/docs/concepts.rst +0 -0
  84. {kinto-20.2.0 → kinto-20.3.0}/docs/conf.py +0 -0
  85. {kinto-20.2.0 → kinto-20.3.0}/docs/configuration/good-practices.rst +0 -0
  86. {kinto-20.2.0 → kinto-20.3.0}/docs/configuration/index.rst +0 -0
  87. {kinto-20.2.0 → kinto-20.3.0}/docs/configuration/production.rst +0 -0
  88. {kinto-20.2.0 → kinto-20.3.0}/docs/configuration/settings.rst +0 -0
  89. {kinto-20.2.0 → kinto-20.3.0}/docs/core/_static/theme_overrides.css +0 -0
  90. {kinto-20.2.0 → kinto-20.3.0}/docs/core/api.rst +0 -0
  91. {kinto-20.2.0 → kinto-20.3.0}/docs/core/cache.rst +0 -0
  92. {kinto-20.2.0 → kinto-20.3.0}/docs/core/decorators.rst +0 -0
  93. {kinto-20.2.0 → kinto-20.3.0}/docs/core/errors.rst +0 -0
  94. {kinto-20.2.0 → kinto-20.3.0}/docs/core/glossary.rst +0 -0
  95. {kinto-20.2.0 → kinto-20.3.0}/docs/core/images/cliquet-base.png +0 -0
  96. {kinto-20.2.0 → kinto-20.3.0}/docs/core/images/cliquet-mozilla.png +0 -0
  97. {kinto-20.2.0 → kinto-20.3.0}/docs/core/index.rst +0 -0
  98. {kinto-20.2.0 → kinto-20.3.0}/docs/core/notifications.rst +0 -0
  99. {kinto-20.2.0 → kinto-20.3.0}/docs/core/permission.rst +0 -0
  100. {kinto-20.2.0 → kinto-20.3.0}/docs/core/quickstart.rst +0 -0
  101. {kinto-20.2.0 → kinto-20.3.0}/docs/core/rationale.rst +0 -0
  102. {kinto-20.2.0 → kinto-20.3.0}/docs/core/resource.rst +0 -0
  103. {kinto-20.2.0 → kinto-20.3.0}/docs/core/storage.rst +0 -0
  104. {kinto-20.2.0 → kinto-20.3.0}/docs/core/testing.rst +0 -0
  105. {kinto-20.2.0 → kinto-20.3.0}/docs/core/utils.rst +0 -0
  106. {kinto-20.2.0 → kinto-20.3.0}/docs/core/viewsets.rst +0 -0
  107. {kinto-20.2.0 → kinto-20.3.0}/docs/faq.rst +0 -0
  108. {kinto-20.2.0 → kinto-20.3.0}/docs/images/alwaysdata-button.svg +0 -0
  109. {kinto-20.2.0 → kinto-20.3.0}/docs/images/architecture.svg +0 -0
  110. {kinto-20.2.0 → kinto-20.3.0}/docs/images/color-formatter.png +0 -0
  111. {kinto-20.2.0 → kinto-20.3.0}/docs/images/concepts-general.png +0 -0
  112. {kinto-20.2.0 → kinto-20.3.0}/docs/images/concepts-permissions.png +0 -0
  113. {kinto-20.2.0 → kinto-20.3.0}/docs/images/heroku-button.png +0 -0
  114. {kinto-20.2.0 → kinto-20.3.0}/docs/images/kinto-logo.png +0 -0
  115. {kinto-20.2.0 → kinto-20.3.0}/docs/images/kinto-logo.svg +0 -0
  116. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-admin.svg +0 -0
  117. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-attachment.svg +0 -0
  118. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-community.svg +0 -0
  119. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-history.svg +0 -0
  120. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-javascript.svg +0 -0
  121. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-jsonschema.svg +0 -0
  122. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-multiapps.svg +0 -0
  123. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-permissions.svg +0 -0
  124. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-python.svg +0 -0
  125. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-selfhostable.svg +0 -0
  126. {kinto-20.2.0 → kinto-20.3.0}/docs/images/logo-synchronisation.svg +0 -0
  127. {kinto-20.2.0 → kinto-20.3.0}/docs/images/overview-deployonce-selfhost.png +0 -0
  128. {kinto-20.2.0 → kinto-20.3.0}/docs/images/overview-features.png +0 -0
  129. {kinto-20.2.0 → kinto-20.3.0}/docs/images/overview-synchronisation.png +0 -0
  130. {kinto-20.2.0 → kinto-20.3.0}/docs/images/overview-use-cases.png +0 -0
  131. {kinto-20.2.0 → kinto-20.3.0}/docs/images/scalingo-button.svg +0 -0
  132. {kinto-20.2.0 → kinto-20.3.0}/docs/images/screenshot-kinto-admin-1.png +0 -0
  133. {kinto-20.2.0 → kinto-20.3.0}/docs/images/screenshot-kinto-admin-2.png +0 -0
  134. {kinto-20.2.0 → kinto-20.3.0}/docs/images/screenshot-kinto-admin-3.png +0 -0
  135. {kinto-20.2.0 → kinto-20.3.0}/docs/images/screenshot-kinto-admin-4.png +0 -0
  136. {kinto-20.2.0 → kinto-20.3.0}/docs/images/sequence-storage.png +0 -0
  137. {kinto-20.2.0 → kinto-20.3.0}/docs/images/sync-both.svg +0 -0
  138. {kinto-20.2.0 → kinto-20.3.0}/docs/images/sync-newest.svg +0 -0
  139. {kinto-20.2.0 → kinto-20.3.0}/docs/images/sync-oldest.svg +0 -0
  140. {kinto-20.2.0 → kinto-20.3.0}/docs/index.rst +0 -0
  141. {kinto-20.2.0 → kinto-20.3.0}/docs/kinto-admin.rst +0 -0
  142. {kinto-20.2.0 → kinto-20.3.0}/docs/overview.rst +0 -0
  143. {kinto-20.2.0 → kinto-20.3.0}/docs/requirements.txt +0 -0
  144. {kinto-20.2.0 → kinto-20.3.0}/docs/troubleshooting.rst +0 -0
  145. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/custom-id-generator.rst +0 -0
  146. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/first-steps.rst +0 -0
  147. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/index.rst +0 -0
  148. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/install.rst +0 -0
  149. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/notifications-custom.rst +0 -0
  150. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/permission-setups.rst +0 -0
  151. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/permissions.rst +0 -0
  152. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/synchronisation.rst +0 -0
  153. {kinto-20.2.0 → kinto-20.3.0}/docs/tutorials/write-plugin.rst +0 -0
  154. {kinto-20.2.0 → kinto-20.3.0}/kinto/__init__.py +0 -0
  155. {kinto-20.2.0 → kinto-20.3.0}/kinto/__main__.py +0 -0
  156. {kinto-20.2.0 → kinto-20.3.0}/kinto/authorization.py +0 -0
  157. {kinto-20.2.0 → kinto-20.3.0}/kinto/config/__init__.py +0 -0
  158. {kinto-20.2.0 → kinto-20.3.0}/kinto/config/kinto.tpl +0 -0
  159. {kinto-20.2.0 → kinto-20.3.0}/kinto/contribute.json +0 -0
  160. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/__init__.py +0 -0
  161. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/authentication.py +0 -0
  162. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/authorization.py +0 -0
  163. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/__init__.py +0 -0
  164. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/memcached.py +0 -0
  165. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/memory.py +0 -0
  166. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/postgresql/__init__.py +0 -0
  167. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/postgresql/schema.sql +0 -0
  168. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cache/testing.py +0 -0
  169. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/__init__.py +0 -0
  170. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/cors.py +0 -0
  171. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/errors.py +0 -0
  172. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/pyramidhook.py +0 -0
  173. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/renderer.py +0 -0
  174. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/resource.py +0 -0
  175. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/service.py +0 -0
  176. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/util.py +0 -0
  177. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/validators/__init__.py +0 -0
  178. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/validators/_colander.py +0 -0
  179. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice/validators/_marshmallow.py +0 -0
  180. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/__init__.py +0 -0
  181. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/converters/__init__.py +0 -0
  182. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/converters/exceptions.py +0 -0
  183. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/converters/parameters.py +0 -0
  184. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/converters/schema.py +0 -0
  185. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/swagger.py +0 -0
  186. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/templates/index.html +0 -0
  187. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/templates/index_script_template.html +0 -0
  188. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/util.py +0 -0
  189. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/cornice_swagger/views.py +0 -0
  190. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/decorators.py +0 -0
  191. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/errors.py +0 -0
  192. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/events.py +0 -0
  193. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/initialization.py +0 -0
  194. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/listeners/__init__.py +0 -0
  195. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/metrics.py +0 -0
  196. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/openapi.py +0 -0
  197. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/__init__.py +0 -0
  198. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/memory.py +0 -0
  199. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/postgresql/__init__.py +0 -0
  200. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/postgresql/migrations/migration_001_002.sql +0 -0
  201. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/postgresql/schema.sql +0 -0
  202. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/permission/testing.py +0 -0
  203. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/resource/__init__.py +0 -0
  204. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/resource/model.py +0 -0
  205. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/resource/schema.py +0 -0
  206. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/resource/viewset.py +0 -0
  207. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/schema.py +0 -0
  208. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/scripts.py +0 -0
  209. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/statsd.py +0 -0
  210. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/__init__.py +0 -0
  211. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/exceptions.py +0 -0
  212. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/generators.py +0 -0
  213. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/client.py +0 -0
  214. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_001_002.sql +0 -0
  215. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_002_003.sql +0 -0
  216. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_003_004.sql +0 -0
  217. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_004_005.sql +0 -0
  218. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_005_006.sql +0 -0
  219. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_006_007.sql +0 -0
  220. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_007_008.sql +0 -0
  221. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_008_009.sql +0 -0
  222. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_009_010.sql +0 -0
  223. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_010_011.sql +0 -0
  224. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_011_012.sql +0 -0
  225. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_012_013.sql +0 -0
  226. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_013_014.sql +0 -0
  227. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_014_015.sql +0 -0
  228. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_015_016.sql +0 -0
  229. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_016_017.sql +0 -0
  230. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_017_018.sql +0 -0
  231. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_018_019.sql +0 -0
  232. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_019_020.sql +0 -0
  233. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_020_021.sql +0 -0
  234. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrations/migration_021_022.sql +0 -0
  235. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/migrator.py +0 -0
  236. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/postgresql/pool.py +0 -0
  237. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/storage/utils.py +0 -0
  238. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/testing.py +0 -0
  239. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/utils.py +0 -0
  240. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/__init__.py +0 -0
  241. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/batch.py +0 -0
  242. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/errors.py +0 -0
  243. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/heartbeat.py +0 -0
  244. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/hello.py +0 -0
  245. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/openapi.py +0 -0
  246. {kinto-20.2.0 → kinto-20.3.0}/kinto/core/views/version.py +0 -0
  247. {kinto-20.2.0 → kinto-20.3.0}/kinto/events.py +0 -0
  248. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/__init__.py +0 -0
  249. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/__init__.py +0 -0
  250. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/authentication.py +0 -0
  251. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/scripts.py +0 -0
  252. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/utils.py +0 -0
  253. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/accounts/views.py +0 -0
  254. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/README.md +0 -0
  255. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/VERSION +0 -0
  256. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/__init__.py +0 -0
  257. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/VERSION +0 -0
  258. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/asn1-EdZsLKOL.js +0 -0
  259. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/clojure-BMjYHr_A.js +0 -0
  260. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/css-BnMrqG3P.js +0 -0
  261. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/index-Cs7JVwIg.css +0 -0
  262. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/index-DwFOt8-z.js +0 -0
  263. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/javascript-qCveANmP.js +0 -0
  264. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/logo-VBRiKSPX.png +0 -0
  265. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/mllike-CXdrOF99.js +0 -0
  266. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/python-BuPzkPfP.js +0 -0
  267. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/rpm-CTu-6PCP.js +0 -0
  268. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/sql-D0XecflT.js +0 -0
  269. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/assets/ttcn-cfg-B9xdYoR4.js +0 -0
  270. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/build/index.html +0 -0
  271. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/public/help.html +0 -0
  272. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/admin/views.py +0 -0
  273. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/default_bucket/__init__.py +0 -0
  274. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/flush.py +0 -0
  275. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/history/__init__.py +0 -0
  276. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/history/views.py +0 -0
  277. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/openid/__init__.py +0 -0
  278. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/openid/utils.py +0 -0
  279. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/openid/views.py +0 -0
  280. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/prometheus.py +0 -0
  281. {kinto-20.2.0 → kinto-20.3.0}/kinto/plugins/statsd.py +0 -0
  282. {kinto-20.2.0 → kinto-20.3.0}/kinto/schema_validation.py +0 -0
  283. {kinto-20.2.0 → kinto-20.3.0}/kinto/views/__init__.py +0 -0
  284. {kinto-20.2.0 → kinto-20.3.0}/kinto/views/admin.py +0 -0
  285. {kinto-20.2.0 → kinto-20.3.0}/kinto/views/buckets.py +0 -0
  286. {kinto-20.2.0 → kinto-20.3.0}/kinto/views/collections.py +0 -0
  287. {kinto-20.2.0 → kinto-20.3.0}/kinto/views/contribute.py +0 -0
  288. {kinto-20.2.0 → kinto-20.3.0}/kinto/views/groups.py +0 -0
  289. {kinto-20.2.0 → kinto-20.3.0}/kinto/views/permissions.py +0 -0
  290. {kinto-20.2.0 → kinto-20.3.0}/kinto/views/records.py +0 -0
  291. {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/dependency_links.txt +0 -0
  292. {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/entry_points.txt +0 -0
  293. {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/requires.txt +0 -0
  294. {kinto-20.2.0 → kinto-20.3.0}/kinto.egg-info/top_level.txt +0 -0
  295. {kinto-20.2.0 → kinto-20.3.0}/pyproject.toml +0 -0
  296. {kinto-20.2.0 → kinto-20.3.0}/scripts/pull-kinto-admin.sh +0 -0
  297. {kinto-20.2.0 → kinto-20.3.0}/setup.cfg +0 -0
  298. {kinto-20.2.0 → kinto-20.3.0}/tests/__init__.py +0 -0
  299. {kinto-20.2.0 → kinto-20.3.0}/tests/browser.ini +0 -0
  300. {kinto-20.2.0 → kinto-20.3.0}/tests/browser.py +0 -0
  301. {kinto-20.2.0 → kinto-20.3.0}/tests/core/__init__.py +0 -0
  302. {kinto-20.2.0 → kinto-20.3.0}/tests/core/listeners.py +0 -0
  303. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/__init__.py +0 -0
  304. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_base.py +0 -0
  305. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_cache_expires.py +0 -0
  306. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_events.py +0 -0
  307. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_filter.py +0 -0
  308. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_model.py +0 -0
  309. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_object.py +0 -0
  310. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_object_permissions.py +0 -0
  311. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_pagination.py +0 -0
  312. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_partial_response.py +0 -0
  313. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_preconditions.py +0 -0
  314. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_schema.py +0 -0
  315. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_sort.py +0 -0
  316. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_sync.py +0 -0
  317. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_views.py +0 -0
  318. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_views_cors.py +0 -0
  319. {kinto-20.2.0 → kinto-20.3.0}/tests/core/resource/test_viewset.py +0 -0
  320. {kinto-20.2.0 → kinto-20.3.0}/tests/core/schema/postgresql-permission-1.sql +0 -0
  321. {kinto-20.2.0 → kinto-20.3.0}/tests/core/schema/postgresql-storage-1.6.sql +0 -0
  322. {kinto-20.2.0 → kinto-20.3.0}/tests/core/schema/postgresql-storage-11.sql +0 -0
  323. {kinto-20.2.0 → kinto-20.3.0}/tests/core/support.py +0 -0
  324. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_authentication.py +0 -0
  325. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_authorization.py +0 -0
  326. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_cache.py +0 -0
  327. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_decorators.py +0 -0
  328. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_deprecation.py +0 -0
  329. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_errors.py +0 -0
  330. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_initialization.py +0 -0
  331. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_listeners.py +0 -0
  332. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_logging.py +0 -0
  333. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_metrics.py +0 -0
  334. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_openapi.py +0 -0
  335. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_permission.py +0 -0
  336. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_schema.py +0 -0
  337. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_scripts.py +0 -0
  338. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_storage.py +0 -0
  339. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_storage_migrations.py +0 -0
  340. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_storage_pool.py +0 -0
  341. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_utils.py +0 -0
  342. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_batch.py +0 -0
  343. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_errors.py +0 -0
  344. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_heartbeat.py +0 -0
  345. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_hello.py +0 -0
  346. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_openapi.py +0 -0
  347. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_postgresql.py +0 -0
  348. {kinto-20.2.0 → kinto-20.3.0}/tests/core/test_views_transaction.py +0 -0
  349. {kinto-20.2.0 → kinto-20.3.0}/tests/core/testapp/__init__.py +0 -0
  350. {kinto-20.2.0 → kinto-20.3.0}/tests/core/testapp/static/index.html +0 -0
  351. {kinto-20.2.0 → kinto-20.3.0}/tests/core/testapp/views.py +0 -0
  352. {kinto-20.2.0 → kinto-20.3.0}/tests/core/testplugin/__init__.py +0 -0
  353. {kinto-20.2.0 → kinto-20.3.0}/tests/functional.ini +0 -0
  354. {kinto-20.2.0 → kinto-20.3.0}/tests/functional.py +0 -0
  355. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/__init__.py +0 -0
  356. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/support.py +0 -0
  357. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_plugins.py +0 -0
  358. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_resources.py +0 -0
  359. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_buckets.py +0 -0
  360. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_collections.py +0 -0
  361. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_errors.py +0 -0
  362. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_groups.py +0 -0
  363. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_responses_records.py +0 -0
  364. {kinto-20.2.0 → kinto-20.3.0}/tests/openapi/test_validation.py +0 -0
  365. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/__init__.py +0 -0
  366. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_accounts.py +0 -0
  367. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_admin.py +0 -0
  368. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_default_bucket.py +0 -0
  369. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_flush.py +0 -0
  370. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_openid.py +0 -0
  371. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_prometheus.py +0 -0
  372. {kinto-20.2.0 → kinto-20.3.0}/tests/plugins/test_statsd.py +0 -0
  373. {kinto-20.2.0 → kinto-20.3.0}/tests/support.py +0 -0
  374. {kinto-20.2.0 → kinto-20.3.0}/tests/test_authorization.py +0 -0
  375. {kinto-20.2.0 → kinto-20.3.0}/tests/test_config.py +0 -0
  376. {kinto-20.2.0 → kinto-20.3.0}/tests/test_configuration/test.ini +0 -0
  377. {kinto-20.2.0 → kinto-20.3.0}/tests/test_init.py +0 -0
  378. {kinto-20.2.0 → kinto-20.3.0}/tests/test_main.py +0 -0
  379. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_admin.py +0 -0
  380. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_buckets.py +0 -0
  381. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_collections.py +0 -0
  382. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_collections_cache.py +0 -0
  383. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_contribute.py +0 -0
  384. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_disable_default.py +0 -0
  385. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_groups.py +0 -0
  386. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_hello.py +0 -0
  387. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_metrics.py +0 -0
  388. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_objects_permissions.py +0 -0
  389. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_permissions.py +0 -0
  390. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_records.py +0 -0
  391. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_schema_collection.py +0 -0
  392. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_schema_group.py +0 -0
  393. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_schema_record.py +0 -0
  394. {kinto-20.2.0 → kinto-20.3.0}/tests/test_views_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinto
3
- Version: 20.2.0
3
+ Version: 20.3.0
4
4
  Summary: Kinto Web Service - Store, Sync, Share, and Self-Host.
5
5
  Author-email: Mozilla Services <developers@kinto-storage.org>
6
6
  License: Copyright 2012 - Mozilla Foundation
@@ -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
- by_parent_id = {
291
+
292
+ timestamps_by_parent_id = {
292
293
  pid: resources
293
- for pid, resources in self._cemetery.items()
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
- for pid, resources in by_parent_id.items():
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():
@@ -79,7 +79,7 @@ class Storage(StorageBase, MigratorMixin):
79
79
 
80
80
  # MigratorMixin attributes.
81
81
  name = "storage"
82
- schema_version = 22
82
+ schema_version = 23
83
83
  schema_file = os.path.join(HERE, "schema.sql")
84
84
  migrations_directory = os.path.join(HERE, "migrations")
85
85
 
@@ -0,0 +1,5 @@
1
+ CREATE INDEX IF NOT EXISTS idx_objects_resource_name_parent_id_deleted
2
+ ON objects(resource_name, parent_id, deleted);
3
+
4
+ -- Bump storage schema version.
5
+ INSERT INTO metadata (name, value) VALUES ('storage_schema_version', '23');
@@ -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', '22');
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.utils import instance_uri
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinto
3
- Version: 20.2.0
3
+ Version: 20.3.0
4
4
  Summary: Kinto Web Service - Store, Sync, Share, and Self-Host.
5
5
  Author-email: Mozilla Services <developers@kinto-storage.org>
6
6
  License: Copyright 2012 - Mozilla Foundation
@@ -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