kinto 20.5.0__tar.gz → 20.6.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.5.0 → kinto-20.6.0}/PKG-INFO +1 -1
  2. {kinto-20.5.0 → kinto-20.6.0}/docs/commandline.rst +20 -0
  3. {kinto-20.5.0 → kinto-20.6.0}/kinto/__main__.py +19 -0
  4. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/scripts.py +15 -0
  5. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/__init__.py +3 -1
  6. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/memory.py +18 -1
  7. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/__init__.py +32 -1
  8. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/testing.py +54 -0
  9. {kinto-20.5.0 → kinto-20.6.0}/kinto.egg-info/PKG-INFO +1 -1
  10. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_scripts.py +17 -0
  11. {kinto-20.5.0 → kinto-20.6.0}/tests/test_main.py +34 -0
  12. {kinto-20.5.0 → kinto-20.6.0}/.dockerignore +0 -0
  13. {kinto-20.5.0 → kinto-20.6.0}/.github/CODE_OF_CONDUCT.md +0 -0
  14. {kinto-20.5.0 → kinto-20.6.0}/.github/CONTRIBUTING.md +0 -0
  15. {kinto-20.5.0 → kinto-20.6.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  16. {kinto-20.5.0 → kinto-20.6.0}/.github/dependabot.yml +0 -0
  17. {kinto-20.5.0 → kinto-20.6.0}/.github/release.yml +0 -0
  18. {kinto-20.5.0 → kinto-20.6.0}/.github/workflows/labels.yaml +0 -0
  19. {kinto-20.5.0 → kinto-20.6.0}/.github/workflows/publish.yml +0 -0
  20. {kinto-20.5.0 → kinto-20.6.0}/.github/workflows/scheduled.yml +0 -0
  21. {kinto-20.5.0 → kinto-20.6.0}/.github/workflows/test.yml +0 -0
  22. {kinto-20.5.0 → kinto-20.6.0}/.gitignore +0 -0
  23. {kinto-20.5.0 → kinto-20.6.0}/.readthedocs.yaml +0 -0
  24. {kinto-20.5.0 → kinto-20.6.0}/CHANGELOG.rst +0 -0
  25. {kinto-20.5.0 → kinto-20.6.0}/CONTRIBUTORS.rst +0 -0
  26. {kinto-20.5.0 → kinto-20.6.0}/Dockerfile +0 -0
  27. {kinto-20.5.0 → kinto-20.6.0}/LICENSE +0 -0
  28. {kinto-20.5.0 → kinto-20.6.0}/Makefile +0 -0
  29. {kinto-20.5.0 → kinto-20.6.0}/README.rst +0 -0
  30. {kinto-20.5.0 → kinto-20.6.0}/SECURITY.md +0 -0
  31. {kinto-20.5.0 → kinto-20.6.0}/SUPPORT.md +0 -0
  32. {kinto-20.5.0 → kinto-20.6.0}/app.wsgi +0 -0
  33. {kinto-20.5.0 → kinto-20.6.0}/constraints.in +0 -0
  34. {kinto-20.5.0 → kinto-20.6.0}/constraints.txt +0 -0
  35. {kinto-20.5.0 → kinto-20.6.0}/docker-compose.yml +0 -0
  36. {kinto-20.5.0 → kinto-20.6.0}/docs/_static/piwik.js +0 -0
  37. {kinto-20.5.0 → kinto-20.6.0}/docs/_static/theme_overrides.css +0 -0
  38. {kinto-20.5.0 → kinto-20.6.0}/docs/_templates/footer.html +0 -0
  39. {kinto-20.5.0 → kinto-20.6.0}/docs/_templates/indexcontent.html +0 -0
  40. {kinto-20.5.0 → kinto-20.6.0}/docs/_templates/layout.html +0 -0
  41. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_details-delete-list.rst +0 -0
  42. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_details-delete-object.rst +0 -0
  43. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_details-get-list.rst +0 -0
  44. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_details-get-object.rst +0 -0
  45. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_details-head-list.rst +0 -0
  46. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_details-patch-object.rst +0 -0
  47. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_details-post-list.rst +0 -0
  48. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_details-put-object.rst +0 -0
  49. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_status-delete-list.rst +0 -0
  50. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_status-delete-object.rst +0 -0
  51. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_status-get-list.rst +0 -0
  52. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_status-get-object.rst +0 -0
  53. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_status-patch-object.rst +0 -0
  54. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_status-post-list.rst +0 -0
  55. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/_status-put-object.rst +0 -0
  56. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/accounts.rst +0 -0
  57. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/admin.rst +0 -0
  58. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/authentication.rst +0 -0
  59. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/backoff.rst +0 -0
  60. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/batch.rst +0 -0
  61. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/buckets.rst +0 -0
  62. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/collections.rst +0 -0
  63. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/deprecation.rst +0 -0
  64. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/errors.rst +0 -0
  65. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/filtering.rst +0 -0
  66. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/flush.rst +0 -0
  67. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/groups.rst +0 -0
  68. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/history.rst +0 -0
  69. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/index.rst +0 -0
  70. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/openapi.rst +0 -0
  71. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/openid.rst +0 -0
  72. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/pagination.rst +0 -0
  73. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/permissions.rst +0 -0
  74. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/records.rst +0 -0
  75. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/selecting_fields.rst +0 -0
  76. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/sorting.rst +0 -0
  77. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/timestamps.rst +0 -0
  78. {kinto-20.5.0 → kinto-20.6.0}/docs/api/1.x/utilities.rst +0 -0
  79. {kinto-20.5.0 → kinto-20.6.0}/docs/api/index.rst +0 -0
  80. {kinto-20.5.0 → kinto-20.6.0}/docs/api/versioning.rst +0 -0
  81. {kinto-20.5.0 → kinto-20.6.0}/docs/changelog.rst +0 -0
  82. {kinto-20.5.0 → kinto-20.6.0}/docs/community.rst +0 -0
  83. {kinto-20.5.0 → kinto-20.6.0}/docs/concepts.rst +0 -0
  84. {kinto-20.5.0 → kinto-20.6.0}/docs/conf.py +0 -0
  85. {kinto-20.5.0 → kinto-20.6.0}/docs/configuration/good-practices.rst +0 -0
  86. {kinto-20.5.0 → kinto-20.6.0}/docs/configuration/index.rst +0 -0
  87. {kinto-20.5.0 → kinto-20.6.0}/docs/configuration/production.rst +0 -0
  88. {kinto-20.5.0 → kinto-20.6.0}/docs/configuration/settings.rst +0 -0
  89. {kinto-20.5.0 → kinto-20.6.0}/docs/core/_static/theme_overrides.css +0 -0
  90. {kinto-20.5.0 → kinto-20.6.0}/docs/core/api.rst +0 -0
  91. {kinto-20.5.0 → kinto-20.6.0}/docs/core/cache.rst +0 -0
  92. {kinto-20.5.0 → kinto-20.6.0}/docs/core/decorators.rst +0 -0
  93. {kinto-20.5.0 → kinto-20.6.0}/docs/core/errors.rst +0 -0
  94. {kinto-20.5.0 → kinto-20.6.0}/docs/core/glossary.rst +0 -0
  95. {kinto-20.5.0 → kinto-20.6.0}/docs/core/images/cliquet-base.png +0 -0
  96. {kinto-20.5.0 → kinto-20.6.0}/docs/core/images/cliquet-mozilla.png +0 -0
  97. {kinto-20.5.0 → kinto-20.6.0}/docs/core/index.rst +0 -0
  98. {kinto-20.5.0 → kinto-20.6.0}/docs/core/notifications.rst +0 -0
  99. {kinto-20.5.0 → kinto-20.6.0}/docs/core/permission.rst +0 -0
  100. {kinto-20.5.0 → kinto-20.6.0}/docs/core/quickstart.rst +0 -0
  101. {kinto-20.5.0 → kinto-20.6.0}/docs/core/rationale.rst +0 -0
  102. {kinto-20.5.0 → kinto-20.6.0}/docs/core/resource.rst +0 -0
  103. {kinto-20.5.0 → kinto-20.6.0}/docs/core/storage.rst +0 -0
  104. {kinto-20.5.0 → kinto-20.6.0}/docs/core/testing.rst +0 -0
  105. {kinto-20.5.0 → kinto-20.6.0}/docs/core/utils.rst +0 -0
  106. {kinto-20.5.0 → kinto-20.6.0}/docs/core/viewsets.rst +0 -0
  107. {kinto-20.5.0 → kinto-20.6.0}/docs/faq.rst +0 -0
  108. {kinto-20.5.0 → kinto-20.6.0}/docs/images/alwaysdata-button.svg +0 -0
  109. {kinto-20.5.0 → kinto-20.6.0}/docs/images/architecture.svg +0 -0
  110. {kinto-20.5.0 → kinto-20.6.0}/docs/images/color-formatter.png +0 -0
  111. {kinto-20.5.0 → kinto-20.6.0}/docs/images/concepts-general.png +0 -0
  112. {kinto-20.5.0 → kinto-20.6.0}/docs/images/concepts-permissions.png +0 -0
  113. {kinto-20.5.0 → kinto-20.6.0}/docs/images/heroku-button.png +0 -0
  114. {kinto-20.5.0 → kinto-20.6.0}/docs/images/kinto-logo.png +0 -0
  115. {kinto-20.5.0 → kinto-20.6.0}/docs/images/kinto-logo.svg +0 -0
  116. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-admin.svg +0 -0
  117. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-attachment.svg +0 -0
  118. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-community.svg +0 -0
  119. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-history.svg +0 -0
  120. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-javascript.svg +0 -0
  121. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-jsonschema.svg +0 -0
  122. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-multiapps.svg +0 -0
  123. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-permissions.svg +0 -0
  124. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-python.svg +0 -0
  125. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-selfhostable.svg +0 -0
  126. {kinto-20.5.0 → kinto-20.6.0}/docs/images/logo-synchronisation.svg +0 -0
  127. {kinto-20.5.0 → kinto-20.6.0}/docs/images/overview-deployonce-selfhost.png +0 -0
  128. {kinto-20.5.0 → kinto-20.6.0}/docs/images/overview-features.png +0 -0
  129. {kinto-20.5.0 → kinto-20.6.0}/docs/images/overview-synchronisation.png +0 -0
  130. {kinto-20.5.0 → kinto-20.6.0}/docs/images/overview-use-cases.png +0 -0
  131. {kinto-20.5.0 → kinto-20.6.0}/docs/images/scalingo-button.svg +0 -0
  132. {kinto-20.5.0 → kinto-20.6.0}/docs/images/screenshot-kinto-admin-1.png +0 -0
  133. {kinto-20.5.0 → kinto-20.6.0}/docs/images/screenshot-kinto-admin-2.png +0 -0
  134. {kinto-20.5.0 → kinto-20.6.0}/docs/images/screenshot-kinto-admin-3.png +0 -0
  135. {kinto-20.5.0 → kinto-20.6.0}/docs/images/screenshot-kinto-admin-4.png +0 -0
  136. {kinto-20.5.0 → kinto-20.6.0}/docs/images/sequence-storage.png +0 -0
  137. {kinto-20.5.0 → kinto-20.6.0}/docs/images/sync-both.svg +0 -0
  138. {kinto-20.5.0 → kinto-20.6.0}/docs/images/sync-newest.svg +0 -0
  139. {kinto-20.5.0 → kinto-20.6.0}/docs/images/sync-oldest.svg +0 -0
  140. {kinto-20.5.0 → kinto-20.6.0}/docs/index.rst +0 -0
  141. {kinto-20.5.0 → kinto-20.6.0}/docs/kinto-admin.rst +0 -0
  142. {kinto-20.5.0 → kinto-20.6.0}/docs/overview.rst +0 -0
  143. {kinto-20.5.0 → kinto-20.6.0}/docs/requirements.txt +0 -0
  144. {kinto-20.5.0 → kinto-20.6.0}/docs/troubleshooting.rst +0 -0
  145. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/custom-id-generator.rst +0 -0
  146. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/first-steps.rst +0 -0
  147. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/index.rst +0 -0
  148. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/install.rst +0 -0
  149. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/notifications-custom.rst +0 -0
  150. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/permission-setups.rst +0 -0
  151. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/permissions.rst +0 -0
  152. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/synchronisation.rst +0 -0
  153. {kinto-20.5.0 → kinto-20.6.0}/docs/tutorials/write-plugin.rst +0 -0
  154. {kinto-20.5.0 → kinto-20.6.0}/kinto/__init__.py +0 -0
  155. {kinto-20.5.0 → kinto-20.6.0}/kinto/authorization.py +0 -0
  156. {kinto-20.5.0 → kinto-20.6.0}/kinto/config/__init__.py +0 -0
  157. {kinto-20.5.0 → kinto-20.6.0}/kinto/config/kinto.tpl +0 -0
  158. {kinto-20.5.0 → kinto-20.6.0}/kinto/contribute.json +0 -0
  159. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/__init__.py +0 -0
  160. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/authentication.py +0 -0
  161. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/authorization.py +0 -0
  162. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cache/__init__.py +0 -0
  163. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cache/memcached.py +0 -0
  164. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cache/memory.py +0 -0
  165. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cache/postgresql/__init__.py +0 -0
  166. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cache/postgresql/schema.sql +0 -0
  167. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cache/testing.py +0 -0
  168. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/__init__.py +0 -0
  169. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/cors.py +0 -0
  170. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/errors.py +0 -0
  171. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/pyramidhook.py +0 -0
  172. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/renderer.py +0 -0
  173. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/resource.py +0 -0
  174. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/service.py +0 -0
  175. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/util.py +0 -0
  176. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/validators/__init__.py +0 -0
  177. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/validators/_colander.py +0 -0
  178. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice/validators/_marshmallow.py +0 -0
  179. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/__init__.py +0 -0
  180. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/converters/__init__.py +0 -0
  181. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/converters/exceptions.py +0 -0
  182. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/converters/parameters.py +0 -0
  183. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/converters/schema.py +0 -0
  184. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/swagger.py +0 -0
  185. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/templates/index.html +0 -0
  186. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/templates/index_script_template.html +0 -0
  187. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/util.py +0 -0
  188. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/cornice_swagger/views.py +0 -0
  189. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/decorators.py +0 -0
  190. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/errors.py +0 -0
  191. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/events.py +0 -0
  192. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/initialization.py +0 -0
  193. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/listeners/__init__.py +0 -0
  194. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/metrics.py +0 -0
  195. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/openapi.py +0 -0
  196. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/permission/__init__.py +0 -0
  197. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/permission/memory.py +0 -0
  198. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/permission/postgresql/__init__.py +0 -0
  199. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/permission/postgresql/migrations/migration_001_002.sql +0 -0
  200. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/permission/postgresql/schema.sql +0 -0
  201. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/permission/testing.py +0 -0
  202. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/resource/__init__.py +0 -0
  203. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/resource/model.py +0 -0
  204. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/resource/schema.py +0 -0
  205. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/resource/viewset.py +0 -0
  206. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/schema.py +0 -0
  207. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/statsd.py +0 -0
  208. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/exceptions.py +0 -0
  209. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/generators.py +0 -0
  210. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/client.py +0 -0
  211. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_001_002.sql +0 -0
  212. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_002_003.sql +0 -0
  213. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_003_004.sql +0 -0
  214. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_004_005.sql +0 -0
  215. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_005_006.sql +0 -0
  216. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_006_007.sql +0 -0
  217. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_007_008.sql +0 -0
  218. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_008_009.sql +0 -0
  219. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_009_010.sql +0 -0
  220. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_010_011.sql +0 -0
  221. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_011_012.sql +0 -0
  222. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_012_013.sql +0 -0
  223. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_013_014.sql +0 -0
  224. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_014_015.sql +0 -0
  225. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_015_016.sql +0 -0
  226. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_016_017.sql +0 -0
  227. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_017_018.sql +0 -0
  228. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_018_019.sql +0 -0
  229. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_019_020.sql +0 -0
  230. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_020_021.sql +0 -0
  231. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_021_022.sql +0 -0
  232. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrations/migration_022_023.sql +0 -0
  233. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/migrator.py +0 -0
  234. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/pool.py +0 -0
  235. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/postgresql/schema.sql +0 -0
  236. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/storage/utils.py +0 -0
  237. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/testing.py +0 -0
  238. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/utils.py +0 -0
  239. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/views/__init__.py +0 -0
  240. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/views/batch.py +0 -0
  241. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/views/errors.py +0 -0
  242. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/views/heartbeat.py +0 -0
  243. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/views/hello.py +0 -0
  244. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/views/openapi.py +0 -0
  245. {kinto-20.5.0 → kinto-20.6.0}/kinto/core/views/version.py +0 -0
  246. {kinto-20.5.0 → kinto-20.6.0}/kinto/events.py +0 -0
  247. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/__init__.py +0 -0
  248. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/accounts/__init__.py +0 -0
  249. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/accounts/authentication.py +0 -0
  250. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/accounts/scripts.py +0 -0
  251. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/accounts/utils.py +0 -0
  252. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/accounts/views.py +0 -0
  253. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/README.md +0 -0
  254. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/VERSION +0 -0
  255. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/__init__.py +0 -0
  256. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/VERSION +0 -0
  257. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/asn1-EdZsLKOL.js +0 -0
  258. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/clojure-BMjYHr_A.js +0 -0
  259. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/css-BnMrqG3P.js +0 -0
  260. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/index-Cs7JVwIg.css +0 -0
  261. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/index-CylsivYB.js +0 -0
  262. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/javascript-qCveANmP.js +0 -0
  263. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/logo-VBRiKSPX.png +0 -0
  264. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/mllike-CXdrOF99.js +0 -0
  265. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/python-BuPzkPfP.js +0 -0
  266. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/rpm-CTu-6PCP.js +0 -0
  267. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/sql-D0XecflT.js +0 -0
  268. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/assets/ttcn-cfg-B9xdYoR4.js +0 -0
  269. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/build/index.html +0 -0
  270. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/public/help.html +0 -0
  271. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/admin/views.py +0 -0
  272. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/default_bucket/__init__.py +0 -0
  273. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/flush.py +0 -0
  274. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/history/__init__.py +0 -0
  275. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/history/listener.py +0 -0
  276. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/history/views.py +0 -0
  277. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/openid/__init__.py +0 -0
  278. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/openid/utils.py +0 -0
  279. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/openid/views.py +0 -0
  280. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/prometheus.py +0 -0
  281. {kinto-20.5.0 → kinto-20.6.0}/kinto/plugins/statsd.py +0 -0
  282. {kinto-20.5.0 → kinto-20.6.0}/kinto/schema_validation.py +0 -0
  283. {kinto-20.5.0 → kinto-20.6.0}/kinto/views/__init__.py +0 -0
  284. {kinto-20.5.0 → kinto-20.6.0}/kinto/views/admin.py +0 -0
  285. {kinto-20.5.0 → kinto-20.6.0}/kinto/views/buckets.py +0 -0
  286. {kinto-20.5.0 → kinto-20.6.0}/kinto/views/collections.py +0 -0
  287. {kinto-20.5.0 → kinto-20.6.0}/kinto/views/contribute.py +0 -0
  288. {kinto-20.5.0 → kinto-20.6.0}/kinto/views/groups.py +0 -0
  289. {kinto-20.5.0 → kinto-20.6.0}/kinto/views/permissions.py +0 -0
  290. {kinto-20.5.0 → kinto-20.6.0}/kinto/views/records.py +0 -0
  291. {kinto-20.5.0 → kinto-20.6.0}/kinto.egg-info/SOURCES.txt +0 -0
  292. {kinto-20.5.0 → kinto-20.6.0}/kinto.egg-info/dependency_links.txt +0 -0
  293. {kinto-20.5.0 → kinto-20.6.0}/kinto.egg-info/entry_points.txt +0 -0
  294. {kinto-20.5.0 → kinto-20.6.0}/kinto.egg-info/requires.txt +0 -0
  295. {kinto-20.5.0 → kinto-20.6.0}/kinto.egg-info/top_level.txt +0 -0
  296. {kinto-20.5.0 → kinto-20.6.0}/pyproject.toml +0 -0
  297. {kinto-20.5.0 → kinto-20.6.0}/scripts/pull-kinto-admin.sh +0 -0
  298. {kinto-20.5.0 → kinto-20.6.0}/setup.cfg +0 -0
  299. {kinto-20.5.0 → kinto-20.6.0}/tests/__init__.py +0 -0
  300. {kinto-20.5.0 → kinto-20.6.0}/tests/browser.ini +0 -0
  301. {kinto-20.5.0 → kinto-20.6.0}/tests/browser.py +0 -0
  302. {kinto-20.5.0 → kinto-20.6.0}/tests/core/__init__.py +0 -0
  303. {kinto-20.5.0 → kinto-20.6.0}/tests/core/listeners.py +0 -0
  304. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/__init__.py +0 -0
  305. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_base.py +0 -0
  306. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_cache_expires.py +0 -0
  307. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_events.py +0 -0
  308. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_filter.py +0 -0
  309. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_model.py +0 -0
  310. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_object.py +0 -0
  311. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_object_permissions.py +0 -0
  312. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_pagination.py +0 -0
  313. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_partial_response.py +0 -0
  314. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_preconditions.py +0 -0
  315. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_schema.py +0 -0
  316. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_sort.py +0 -0
  317. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_sync.py +0 -0
  318. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_views.py +0 -0
  319. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_views_cors.py +0 -0
  320. {kinto-20.5.0 → kinto-20.6.0}/tests/core/resource/test_viewset.py +0 -0
  321. {kinto-20.5.0 → kinto-20.6.0}/tests/core/schema/postgresql-permission-1.sql +0 -0
  322. {kinto-20.5.0 → kinto-20.6.0}/tests/core/schema/postgresql-storage-1.6.sql +0 -0
  323. {kinto-20.5.0 → kinto-20.6.0}/tests/core/schema/postgresql-storage-11.sql +0 -0
  324. {kinto-20.5.0 → kinto-20.6.0}/tests/core/support.py +0 -0
  325. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_authentication.py +0 -0
  326. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_authorization.py +0 -0
  327. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_cache.py +0 -0
  328. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_decorators.py +0 -0
  329. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_deprecation.py +0 -0
  330. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_errors.py +0 -0
  331. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_initialization.py +0 -0
  332. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_listeners.py +0 -0
  333. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_logging.py +0 -0
  334. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_metrics.py +0 -0
  335. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_openapi.py +0 -0
  336. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_permission.py +0 -0
  337. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_schema.py +0 -0
  338. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_storage.py +0 -0
  339. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_storage_migrations.py +0 -0
  340. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_storage_pool.py +0 -0
  341. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_utils.py +0 -0
  342. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_views_batch.py +0 -0
  343. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_views_errors.py +0 -0
  344. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_views_heartbeat.py +0 -0
  345. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_views_hello.py +0 -0
  346. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_views_openapi.py +0 -0
  347. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_views_postgresql.py +0 -0
  348. {kinto-20.5.0 → kinto-20.6.0}/tests/core/test_views_transaction.py +0 -0
  349. {kinto-20.5.0 → kinto-20.6.0}/tests/core/testapp/__init__.py +0 -0
  350. {kinto-20.5.0 → kinto-20.6.0}/tests/core/testapp/static/index.html +0 -0
  351. {kinto-20.5.0 → kinto-20.6.0}/tests/core/testapp/views.py +0 -0
  352. {kinto-20.5.0 → kinto-20.6.0}/tests/core/testplugin/__init__.py +0 -0
  353. {kinto-20.5.0 → kinto-20.6.0}/tests/functional.ini +0 -0
  354. {kinto-20.5.0 → kinto-20.6.0}/tests/functional.py +0 -0
  355. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/__init__.py +0 -0
  356. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/support.py +0 -0
  357. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/test_plugins.py +0 -0
  358. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/test_resources.py +0 -0
  359. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/test_responses_buckets.py +0 -0
  360. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/test_responses_collections.py +0 -0
  361. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/test_responses_errors.py +0 -0
  362. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/test_responses_groups.py +0 -0
  363. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/test_responses_records.py +0 -0
  364. {kinto-20.5.0 → kinto-20.6.0}/tests/openapi/test_validation.py +0 -0
  365. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/__init__.py +0 -0
  366. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/test_accounts.py +0 -0
  367. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/test_admin.py +0 -0
  368. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/test_default_bucket.py +0 -0
  369. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/test_flush.py +0 -0
  370. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/test_history.py +0 -0
  371. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/test_openid.py +0 -0
  372. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/test_prometheus.py +0 -0
  373. {kinto-20.5.0 → kinto-20.6.0}/tests/plugins/test_statsd.py +0 -0
  374. {kinto-20.5.0 → kinto-20.6.0}/tests/support.py +0 -0
  375. {kinto-20.5.0 → kinto-20.6.0}/tests/test_authorization.py +0 -0
  376. {kinto-20.5.0 → kinto-20.6.0}/tests/test_config.py +0 -0
  377. {kinto-20.5.0 → kinto-20.6.0}/tests/test_configuration/test.ini +0 -0
  378. {kinto-20.5.0 → kinto-20.6.0}/tests/test_init.py +0 -0
  379. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_admin.py +0 -0
  380. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_buckets.py +0 -0
  381. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_collections.py +0 -0
  382. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_collections_cache.py +0 -0
  383. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_contribute.py +0 -0
  384. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_disable_default.py +0 -0
  385. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_groups.py +0 -0
  386. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_hello.py +0 -0
  387. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_metrics.py +0 -0
  388. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_objects_permissions.py +0 -0
  389. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_permissions.py +0 -0
  390. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_records.py +0 -0
  391. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_schema_collection.py +0 -0
  392. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_schema_group.py +0 -0
  393. {kinto-20.5.0 → kinto-20.6.0}/tests/test_views_schema_record.py +0 -0
  394. {kinto-20.5.0 → kinto-20.6.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.5.0
3
+ Version: 20.6.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
@@ -96,3 +96,23 @@ For example:
96
96
  ::
97
97
 
98
98
  kinto flush-cache --ini kinto.ini
99
+
100
+
101
+ Purge Deleted
102
+ -------------
103
+
104
+ Delete tombstones from the database, and keep a certain number of them per collection.
105
+
106
+ This is **not** recommended in production, unless you really know what you are doing.
107
+
108
+ It will break partial synchronization on clients that didn't have the chance to apply these deletions locally.
109
+
110
+ ::
111
+
112
+ usage: kinto purge-deleted [-h] [--ini INI_FILE] [-q] [-v] resources [resources ...] max-retained
113
+
114
+ For example:
115
+
116
+ ::
117
+
118
+ kinto --ini=config/postgresql.ini purge-deleted bucket,collection,record 10000
@@ -33,6 +33,7 @@ def main(args=None):
33
33
  "flush-cache",
34
34
  "version",
35
35
  "create-user",
36
+ "purge-deleted",
36
37
  )
37
38
  subparsers = parser.add_subparsers(
38
39
  title="subcommands",
@@ -128,6 +129,18 @@ def main(args=None):
128
129
  subparser.add_argument(
129
130
  "-p", "--password", help="Superuser password", required=False, default=None
130
131
  )
132
+ elif command == "purge-deleted":
133
+ subparser.add_argument(
134
+ "resources", # No '--' → positional
135
+ nargs="+", # Accepts one or more
136
+ help="List of resources (e.g. record bucket group)",
137
+ default=["record"],
138
+ )
139
+ subparser.add_argument(
140
+ "max-retained",
141
+ help="The maximum number of tombstones to keep per resource and per parent",
142
+ type=int,
143
+ )
131
144
 
132
145
  # Parse command-line arguments
133
146
  parsed_args = vars(parser.parse_args(args))
@@ -208,6 +221,12 @@ def main(args=None):
208
221
  env = bootstrap(config_file, options={"command": "create-user"})
209
222
  return accounts_scripts.create_user(env, username=username, password=password)
210
223
 
224
+ elif which_command == "purge-deleted":
225
+ env = bootstrap(config_file)
226
+ return core_scripts.purge_deleted(
227
+ env, parsed_args["resources"], parsed_args["max-retained"]
228
+ )
229
+
211
230
  elif which_command == "start":
212
231
  pserve_argv = ["pserve"]
213
232
 
@@ -28,6 +28,21 @@ def migrate(env, dry_run=False):
28
28
  getattr(registry, backend).initialize_schema(dry_run=dry_run)
29
29
 
30
30
 
31
+ def purge_deleted(env, resource_names, max_retained):
32
+ logger.info("Keep only %r tombstones per parent and resource." % max_retained)
33
+
34
+ registry = env["registry"]
35
+
36
+ count = 0
37
+ for resource_name in resource_names:
38
+ count += registry.storage.purge_deleted(
39
+ resource_name=resource_name, parent_id="*", max_retained=max_retained
40
+ )
41
+
42
+ logger.info("%s tombstone(s) deleted." % count)
43
+ return 0
44
+
45
+
31
46
  def flush_cache(env):
32
47
  registry = env["registry"]
33
48
  registry.cache.flush()
@@ -264,6 +264,7 @@ class StorageBase:
264
264
  resource_name,
265
265
  parent_id,
266
266
  before=None,
267
+ max_retained=None,
267
268
  id_field=DEFAULT_ID_FIELD,
268
269
  modified_field=DEFAULT_MODIFIED_FIELD,
269
270
  ):
@@ -273,7 +274,8 @@ class StorageBase:
273
274
  :param str resource_name: the resource name.
274
275
  :param str parent_id: the resource parent.
275
276
 
276
- :param int before: Optionnal timestamp to limit deletion (exclusive)
277
+ :param int before: Optional timestamp to limit deletion (exclusive).
278
+ :param int max_count: Optional maximum of tombstones to keep per collection.
277
279
 
278
280
  :returns: The number of deleted objects.
279
281
  :rtype: int
@@ -284,9 +284,13 @@ class Storage(MemoryBasedStorage):
284
284
  resource_name,
285
285
  parent_id,
286
286
  before=None,
287
+ max_retained=None,
287
288
  id_field=DEFAULT_ID_FIELD,
288
289
  modified_field=DEFAULT_MODIFIED_FIELD,
289
290
  ):
291
+ if max_retained is not None and before is not None:
292
+ raise ValueError("`before` and `max_retained` are exclusive arguments. Pick one.")
293
+
290
294
  parent_id_match = re.compile(parent_id.replace("*", ".*"))
291
295
 
292
296
  timestamps_by_parent_id = {
@@ -312,7 +316,20 @@ class Storage(MemoryBasedStorage):
312
316
  resources = {resource_name: resources[resource_name]}
313
317
  for resource, resource_objects in resources.items():
314
318
  if before is None:
315
- kept = {}
319
+ if max_retained is None:
320
+ kept = {}
321
+ else:
322
+ kept = {
323
+ key: value
324
+ for i, (key, value) in enumerate(
325
+ sorted(
326
+ resource_objects.items(),
327
+ key=lambda i: i[1]["last_modified"],
328
+ reverse=True,
329
+ )
330
+ )
331
+ if i < max_retained
332
+ }
316
333
  else:
317
334
  kept = {
318
335
  key: value
@@ -598,6 +598,7 @@ class Storage(StorageBase, MigratorMixin):
598
598
  resource_name,
599
599
  parent_id,
600
600
  before=None,
601
+ max_retained=None,
601
602
  id_field=DEFAULT_ID_FIELD,
602
603
  modified_field=DEFAULT_MODIFIED_FIELD,
603
604
  ):
@@ -608,9 +609,39 @@ class Storage(StorageBase, MigratorMixin):
608
609
  {resource_name_filter}
609
610
  {conditions_filter}
610
611
  """
612
+
613
+ if max_retained is not None:
614
+ if before is not None:
615
+ raise ValueError("`before` and `max_retained` are exclusive arguments. Pick one.")
616
+
617
+ delete_tombstones = """
618
+ WITH ranked AS (
619
+ SELECT
620
+ id AS objid,
621
+ parent_id,
622
+ resource_name,
623
+ ROW_NUMBER() OVER (
624
+ PARTITION BY parent_id, resource_name
625
+ ORDER BY last_modified DESC
626
+ ) AS rn
627
+ FROM objects
628
+ )
629
+ DELETE FROM objects
630
+ WHERE id IN (
631
+ SELECT objid
632
+ FROM ranked
633
+ WHERE
634
+ {parent_id_filter}
635
+ {resource_name_filter}
636
+ AND rn > :max_retained
637
+ )
638
+ """
639
+
611
640
  id_field = id_field or self.id_field
612
641
  modified_field = modified_field or self.modified_field
613
- placeholders = dict(parent_id=parent_id, resource_name=resource_name)
642
+ placeholders = dict(
643
+ parent_id=parent_id, resource_name=resource_name, max_retained=max_retained
644
+ )
614
645
  # Safe strings
615
646
  safeholders = defaultdict(str)
616
647
  # Handle parent_id as a regex only if it contains *
@@ -1342,6 +1342,60 @@ class DeletedObjectsTest:
1342
1342
  count = self.storage.count_all(**self.storage_kw)
1343
1343
  self.assertEqual(count, 0)
1344
1344
 
1345
+ def test_purge_deleted_does_not_support_before_and_max_retained(self):
1346
+ self.assertRaises(
1347
+ ValueError,
1348
+ self.storage.purge_deleted,
1349
+ resource_name="r",
1350
+ parent_id="p",
1351
+ before=42,
1352
+ max_retained=1,
1353
+ )
1354
+
1355
+ def test_purge_deleted_remove_with_max_count_per_collection(self):
1356
+ cid1_kw = dict(parent_id="cid1", resource_name="one")
1357
+ for i in range(5):
1358
+ record = self.create_object(**cid1_kw)
1359
+ self.storage.delete(object_id=record["id"], **cid1_kw)
1360
+ cid2_kw = dict(parent_id="cid2", resource_name="one")
1361
+ for i in range(4):
1362
+ record = self.create_object(**cid2_kw)
1363
+ self.storage.delete(object_id=record["id"], **cid2_kw)
1364
+ cid1_other_kw = dict(parent_id="cid1", resource_name="other")
1365
+ for i in range(5):
1366
+ record = self.create_object(**cid1_other_kw)
1367
+ self.storage.delete(object_id=record["id"], **cid1_other_kw)
1368
+
1369
+ # Consistency checks first.
1370
+ objects_c1_before = self.storage.list_all(include_deleted=True, **cid1_kw)
1371
+ self.assertEqual(len(objects_c1_before), 5)
1372
+ objects_c2_before = self.storage.list_all(include_deleted=True, **cid2_kw)
1373
+ self.assertEqual(len(objects_c2_before), 4)
1374
+ objects_c1_other_before = self.storage.list_all(include_deleted=True, **cid1_other_kw)
1375
+ self.assertEqual(len(objects_c1_other_before), 5)
1376
+
1377
+ num_removed = self.storage.purge_deleted(
1378
+ resource_name="one", parent_id="*", max_retained=3
1379
+ )
1380
+
1381
+ self.assertEqual(num_removed, 3) # 2 for one/cid1, 1 for one/cid2, 0 for other/cid1
1382
+
1383
+ # It kept 3 tombstones for each resource/parent.
1384
+ objects = self.storage.list_all(include_deleted=True, **cid1_kw)
1385
+ self.assertEqual(len(objects), 3)
1386
+ self.assertEqual(
1387
+ min(obj["last_modified"] for obj in objects), objects_c1_before[2]["last_modified"]
1388
+ )
1389
+
1390
+ objects = self.storage.list_all(include_deleted=True, **cid2_kw)
1391
+ self.assertEqual(len(objects), 3)
1392
+ self.assertNotEqual(
1393
+ min(obj["last_modified"] for obj in objects), objects_c2_before[2]["last_modified"]
1394
+ )
1395
+
1396
+ objects = self.storage.list_all(include_deleted=True, **cid1_other_kw)
1397
+ self.assertEqual(len(objects), 5)
1398
+
1345
1399
  #
1346
1400
  # Sorting
1347
1401
  #
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinto
3
- Version: 20.5.0
3
+ Version: 20.6.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
@@ -45,3 +45,20 @@ class InitSchemaTest(unittest.TestCase):
45
45
  scripts.flush_cache({"registry": self.registry})
46
46
  reg = self.registry
47
47
  reg.cache.flush.assert_called_with()
48
+
49
+
50
+ class PurgeDeletedTest(unittest.TestCase):
51
+ def setUp(self):
52
+ self.registry = mock.MagicMock()
53
+
54
+ def test_purge_deleted(self):
55
+ code = scripts.purge_deleted(
56
+ {"registry": self.registry}, resource_names=["A", "B"], max_retained=42
57
+ )
58
+ assert code == 0
59
+ self.registry.storage.purge_deleted.assert_any_call(
60
+ parent_id="*", resource_name="A", max_retained=42
61
+ )
62
+ self.registry.storage.purge_deleted.assert_any_call(
63
+ parent_id="*", resource_name="B", max_retained=42
64
+ )
@@ -312,3 +312,37 @@ class TestMain(unittest.TestCase):
312
312
  res = main(["flush-cache", "--ini", TEMP_KINTO_INI])
313
313
  assert res == 0
314
314
  assert mocked_cache_script.call_count == 1
315
+
316
+ def test_cli_purge_deleted_runs_purge_deleted_script(self):
317
+ with mock.patch("kinto.core.scripts.purge_deleted") as purge_deleted:
318
+ purge_deleted.return_value = mock.sentinel.purge_deleted
319
+ main(
320
+ [
321
+ "init",
322
+ "--ini",
323
+ TEMP_KINTO_INI,
324
+ "--backend",
325
+ "memory",
326
+ "--cache-backend",
327
+ "memory",
328
+ ]
329
+ )
330
+ res = main(["purge-deleted", "--ini", TEMP_KINTO_INI, "record,bucket", "42"])
331
+ assert res == mock.sentinel.purge_deleted
332
+ assert purge_deleted.call_count == 1
333
+
334
+ def test_cli_purge_deleted_fails_if_no_max_retained(self):
335
+ with mock.patch("kinto.core.scripts.purge_deleted"):
336
+ main(
337
+ [
338
+ "init",
339
+ "--ini",
340
+ TEMP_KINTO_INI,
341
+ "--backend",
342
+ "memory",
343
+ "--cache-backend",
344
+ "memory",
345
+ ]
346
+ )
347
+ with self.assertRaises(SystemExit):
348
+ main(["purge-deleted", "--ini", TEMP_KINTO_INI, "record,bucket"])
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