kinto 20.5.0__py3-none-any.whl → 20.6.0__py3-none-any.whl
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/__main__.py +19 -0
- kinto/core/scripts.py +15 -0
- kinto/core/storage/__init__.py +3 -1
- kinto/core/storage/memory.py +18 -1
- kinto/core/storage/postgresql/__init__.py +32 -1
- kinto/core/storage/testing.py +54 -0
- {kinto-20.5.0.dist-info → kinto-20.6.0.dist-info}/METADATA +1 -1
- {kinto-20.5.0.dist-info → kinto-20.6.0.dist-info}/RECORD +12 -12
- {kinto-20.5.0.dist-info → kinto-20.6.0.dist-info}/WHEEL +0 -0
- {kinto-20.5.0.dist-info → kinto-20.6.0.dist-info}/entry_points.txt +0 -0
- {kinto-20.5.0.dist-info → kinto-20.6.0.dist-info}/licenses/LICENSE +0 -0
- {kinto-20.5.0.dist-info → kinto-20.6.0.dist-info}/top_level.txt +0 -0
kinto/__main__.py
CHANGED
|
@@ -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
|
|
kinto/core/scripts.py
CHANGED
|
@@ -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()
|
kinto/core/storage/__init__.py
CHANGED
|
@@ -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:
|
|
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
|
kinto/core/storage/memory.py
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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 *
|
kinto/core/storage/testing.py
CHANGED
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
kinto/__init__.py,sha256=XG7cOyHN0ipQeOSPMW-XFax7uFgAjnCd3Slq0jSFGC4,3299
|
|
2
|
-
kinto/__main__.py,sha256=
|
|
2
|
+
kinto/__main__.py,sha256=6XDjWYGtJ36qepn9Of4xL9zc35TfdSeEvHh3s26Aw9c,7913
|
|
3
3
|
kinto/authorization.py,sha256=i3ttdPTFGzE9CqUQmfC4rR_6dDZJu0jWJMLGl_jFzIE,4919
|
|
4
4
|
kinto/contribute.json,sha256=HT9QVB8rA8jWIREQoqWfMibfJXMAzbRsixW8F6O6cQY,792
|
|
5
5
|
kinto/events.py,sha256=NMPvKUdbi25aYHhu9svzQsrEZMa9nyO4mtuMZC5871Q,85
|
|
@@ -16,7 +16,7 @@ kinto/core/initialization.py,sha256=ZjmCKc8MYZXv63W2mv--fY8rXSLAnJa7RtCYdfK4jsg,
|
|
|
16
16
|
kinto/core/metrics.py,sha256=Y6Mt4PUzy2-oudeGr_oCmtX8nIR4SZkzUlPxr58jr-g,2619
|
|
17
17
|
kinto/core/openapi.py,sha256=92sZviff4NCxN0jMnu5lPUnF5iQbrKMGy7Cegf-VAME,3876
|
|
18
18
|
kinto/core/schema.py,sha256=d5L5TQynRYJPkZ8Mu2X7F72xEh6SKDbrHK1CNTdOf2E,3646
|
|
19
|
-
kinto/core/scripts.py,sha256=
|
|
19
|
+
kinto/core/scripts.py,sha256=02SXVjo579W82AsDF8dyVCRxYVcrMFkjjaNVIgLChh0,1412
|
|
20
20
|
kinto/core/statsd.py,sha256=2f4s2opiHVdrA02ZlBa5pxIHaEjPuG8tdVLsmdII27s,64
|
|
21
21
|
kinto/core/testing.py,sha256=kZ-75EiiZwTNDBpHZyKBBHute6jEmUwXd7nRMK9kwr4,5908
|
|
22
22
|
kinto/core/utils.py,sha256=EDlZfHsgcaUvxg-AG_dn9IcQCq7j7RxpBjnFrx8b11E,17069
|
|
@@ -58,13 +58,13 @@ kinto/core/resource/__init__.py,sha256=B-1X3nPzIZOiN8Dc_tSersAP-Wn4jXf3GcrctN2Ck
|
|
|
58
58
|
kinto/core/resource/model.py,sha256=xjZ6shnhelXCdWvgw6yeOWXodxiKMm9iyDqLTk0i8Bs,15626
|
|
59
59
|
kinto/core/resource/schema.py,sha256=EhPKDMlBjx60hXztMvywfo4IrRfwaZ3V-7sY_sl-BYk,16126
|
|
60
60
|
kinto/core/resource/viewset.py,sha256=Wo7mQwmI08IGnSetaqGF66fCqYPB1pDUdZa3U92NIic,7613
|
|
61
|
-
kinto/core/storage/__init__.py,sha256=
|
|
61
|
+
kinto/core/storage/__init__.py,sha256=xELryXjUvmYon4qS3GJB6nCgVcLyGJoV0rJ5EWjGiVY,13816
|
|
62
62
|
kinto/core/storage/exceptions.py,sha256=o10f7LohwyCHOTlR-dOdnB4us_MdCGOJUxZO8HZ3Akc,1304
|
|
63
63
|
kinto/core/storage/generators.py,sha256=rxWN9hOfOsB-PLeryhPGO-aE670sivr3LFRIExImpxc,1829
|
|
64
|
-
kinto/core/storage/memory.py,sha256
|
|
65
|
-
kinto/core/storage/testing.py,sha256=
|
|
64
|
+
kinto/core/storage/memory.py,sha256=--08jHpnFMtcyz1CD10ZFni36qe-Wzrwprl-VVI84IE,20630
|
|
65
|
+
kinto/core/storage/testing.py,sha256=5t61Li0M6XXCNu0vRMaT-_GaBNz7TPZ27eXTO1Bf9vI,80766
|
|
66
66
|
kinto/core/storage/utils.py,sha256=BHpohIKVOCtURjRbUT7O5AEhIKfSEFv-pfgRzq8Q5zs,1082
|
|
67
|
-
kinto/core/storage/postgresql/__init__.py,sha256=
|
|
67
|
+
kinto/core/storage/postgresql/__init__.py,sha256=egK7K5oQgRF03LxdQAzPj4L4yeysDVkkPBKZg5Ab4mQ,41111
|
|
68
68
|
kinto/core/storage/postgresql/client.py,sha256=JTRxHK-8ipTXgs0E4PyiFiun-32yuzqFGsWTi-OuFfA,4270
|
|
69
69
|
kinto/core/storage/postgresql/migrator.py,sha256=MQ_5aSrDi9-s2wlyyFyfhYP6HreCXjtlJzBI4b85u1I,3524
|
|
70
70
|
kinto/core/storage/postgresql/pool.py,sha256=lOtclVagFqzzWbVxrGoWeKylpHlKdFgGz3Ef6cgGNJU,2219
|
|
@@ -141,9 +141,9 @@ kinto/views/contribute.py,sha256=PJoIMLj9_IszSjgZkaCd_TUjekDgNqjpmVTmRN9ztaA,983
|
|
|
141
141
|
kinto/views/groups.py,sha256=jOq5fX0-4lwZE8k1q5HME2tU7x9052rtBPF7YqcJ-Qg,3181
|
|
142
142
|
kinto/views/permissions.py,sha256=F0_eKx201WyLonXJ5vLdGKa9RcFKjvAihrEEhU1JuLw,9069
|
|
143
143
|
kinto/views/records.py,sha256=lYfACW2L8qcQoyYBD5IX-fTPjFWmGp7GjHq_U4InlyE,5037
|
|
144
|
-
kinto-20.
|
|
145
|
-
kinto-20.
|
|
146
|
-
kinto-20.
|
|
147
|
-
kinto-20.
|
|
148
|
-
kinto-20.
|
|
149
|
-
kinto-20.
|
|
144
|
+
kinto-20.6.0.dist-info/licenses/LICENSE,sha256=oNEIMTuTJzppR5ZEyi86yvvtSagveMYXTYFn56zF0Uk,561
|
|
145
|
+
kinto-20.6.0.dist-info/METADATA,sha256=ajUNuc134EmgReYQWZIKBRKtn6gWQEeIEEj8O1MQ3KU,8731
|
|
146
|
+
kinto-20.6.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
147
|
+
kinto-20.6.0.dist-info/entry_points.txt,sha256=3KlqBWPKY81mrCe_oX0I5s1cRO7Q53nCLbnVr5P9LH4,85
|
|
148
|
+
kinto-20.6.0.dist-info/top_level.txt,sha256=EG_YmbZL6FAug9VwopG7JtF9SvH_r0DEnFp-3twPPys,6
|
|
149
|
+
kinto-20.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|