kinto 20.2.0__py3-none-any.whl → 20.3.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.

@@ -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
@@ -61,14 +61,14 @@ kinto/core/resource/viewset.py,sha256=Wo7mQwmI08IGnSetaqGF66fCqYPB1pDUdZa3U92NIi
61
61
  kinto/core/storage/__init__.py,sha256=Bo9q5PCDQ7KkBuBAHt7UoZFr5WfVWwEbFJjGsuq4Oo4,13704
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=1M3kq6OC9MD-WR-nwm34U0R7AYcg54ZEAfGzmZX9_fY,19392
65
- kinto/core/storage/testing.py,sha256=Jg72tGWVS3pWNhIGY8YUTp99_wqSwL6-6TMhc2InpLM,77977
64
+ kinto/core/storage/memory.py,sha256=DR6gpqSYh4oGPBX9x4-dGLNITZi33T0VODsNWsPtgho,19875
65
+ kinto/core/storage/testing.py,sha256=4hGR6xc54M6H81IQb8XP7NP_iBP-X8m8PqKqixDs5nU,78411
66
66
  kinto/core/storage/utils.py,sha256=BHpohIKVOCtURjRbUT7O5AEhIKfSEFv-pfgRzq8Q5zs,1082
67
- kinto/core/storage/postgresql/__init__.py,sha256=GCnf1vWSrxGT1Dd9n9pLTK2064ukctLpKNHVQUMKd0Y,40157
67
+ kinto/core/storage/postgresql/__init__.py,sha256=HMlUfTBsnsUQkLEhuhItU-9fgSOf9wTH6J09-1HW1fA,40157
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
71
- kinto/core/storage/postgresql/schema.sql,sha256=4q0NpjaX2GoiuGNnIVpMVuox9vQHGhh64qE4Z4a2FdQ,4095
71
+ kinto/core/storage/postgresql/schema.sql,sha256=qRmx8NSwqedwozOi-Nn-pnipubxUWLCcYTRDMkt2B80,4216
72
72
  kinto/core/storage/postgresql/migrations/migration_001_002.sql,sha256=GVJIs8MGmZEyp1i0KjsmGKv1pLlBRckn0EWX6Kl6uQE,428
73
73
  kinto/core/storage/postgresql/migrations/migration_002_003.sql,sha256=zlSZpG_2L-wd8KXh3szmbYtoGjAOOwy2gH7RFMUd61w,1203
74
74
  kinto/core/storage/postgresql/migrations/migration_003_004.sql,sha256=OZSbH6Yt1PA2zba8iQWIguaTsnn3v7bFF-rbg2_teXY,530
@@ -90,6 +90,7 @@ kinto/core/storage/postgresql/migrations/migration_018_019.sql,sha256=XK6ex2jwQQ
90
90
  kinto/core/storage/postgresql/migrations/migration_019_020.sql,sha256=yDQDdzU65cgctKIeo1YqMrFi7aU2VGdBhVUpVQAQOgM,306
91
91
  kinto/core/storage/postgresql/migrations/migration_020_021.sql,sha256=cEgfGNDLH3RyLswxy9YZazZtOvidxNsi57z7SGR8VsQ,2369
92
92
  kinto/core/storage/postgresql/migrations/migration_021_022.sql,sha256=MUIAfgbBDQLy8JfklEs6ekK93nr1buVRtmBAHaaMXug,1993
93
+ kinto/core/storage/postgresql/migrations/migration_022_023.sql,sha256=c1Fw10sxtaDqYJpxk1P9eZCtXP3uSmBY7-3IjZZLwjU,231
93
94
  kinto/core/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
95
  kinto/core/views/batch.py,sha256=sWNn67YqfTbiqwpbwbm1QyumcVGi8aNhlT5AtLToElI,5657
95
96
  kinto/core/views/errors.py,sha256=2uTy85UBQL1EDIMotzFBhVl14RmWwb3rupbsB0mycbs,6099
@@ -127,7 +128,7 @@ kinto/plugins/admin/build/assets/ttcn-cfg-B9xdYoR4.js,sha256=jTZ_XHKOChJZEt-FzJb
127
128
  kinto/plugins/admin/public/help.html,sha256=1hol7z5Sv0Xn3mYyEfPQWFOsrR24htlKhmnGA3rH8fs,958
128
129
  kinto/plugins/default_bucket/__init__.py,sha256=7TmBzFgW0gmYsetXr6Kqt9317XZ3PiB9gyCr-IBfmGg,7276
129
130
  kinto/plugins/history/__init__.py,sha256=s-RMNWaZlmBNd4sNsgJ-hDvMW4pPKUZ-sdZYubb0Kdo,1015
130
- kinto/plugins/history/listener.py,sha256=Tq5ZHpIOIzQs9yPXA1exEftPoYCuFQJvgxbaIb6XBrM,5033
131
+ kinto/plugins/history/listener.py,sha256=WuMky5f3Ek6NaRQODTO-kLSD9mftJwCvxHeQ7Vy4_JA,7628
131
132
  kinto/plugins/history/views.py,sha256=NoBP-S7epeH5TLZZbIqfBmwMA2KaWmxP7lqPAS11BTU,2293
132
133
  kinto/plugins/openid/__init__.py,sha256=1Iv5SCa6vwEvoJkmGd45-TYm_mxz2okFj6u2VTNuVrk,4863
133
134
  kinto/plugins/openid/utils.py,sha256=n3KGS-ogXR2sg6j4QtdPe_DtEsqD7AGVT2K7vhl8yE8,273
@@ -140,9 +141,9 @@ kinto/views/contribute.py,sha256=PJoIMLj9_IszSjgZkaCd_TUjekDgNqjpmVTmRN9ztaA,983
140
141
  kinto/views/groups.py,sha256=jOq5fX0-4lwZE8k1q5HME2tU7x9052rtBPF7YqcJ-Qg,3181
141
142
  kinto/views/permissions.py,sha256=F0_eKx201WyLonXJ5vLdGKa9RcFKjvAihrEEhU1JuLw,9069
142
143
  kinto/views/records.py,sha256=lYfACW2L8qcQoyYBD5IX-fTPjFWmGp7GjHq_U4InlyE,5037
143
- kinto-20.2.0.dist-info/licenses/LICENSE,sha256=oNEIMTuTJzppR5ZEyi86yvvtSagveMYXTYFn56zF0Uk,561
144
- kinto-20.2.0.dist-info/METADATA,sha256=Yyclbux2PXFDz3EScffA46qvaSPWAv8HzBktTyPYOrc,8731
145
- kinto-20.2.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
146
- kinto-20.2.0.dist-info/entry_points.txt,sha256=3KlqBWPKY81mrCe_oX0I5s1cRO7Q53nCLbnVr5P9LH4,85
147
- kinto-20.2.0.dist-info/top_level.txt,sha256=EG_YmbZL6FAug9VwopG7JtF9SvH_r0DEnFp-3twPPys,6
148
- kinto-20.2.0.dist-info/RECORD,,
144
+ kinto-20.3.0.dist-info/licenses/LICENSE,sha256=oNEIMTuTJzppR5ZEyi86yvvtSagveMYXTYFn56zF0Uk,561
145
+ kinto-20.3.0.dist-info/METADATA,sha256=WFcv90kV4fQrRr1nR8GQknnmY6Zo9_n1wk1Z-M7nYXg,8731
146
+ kinto-20.3.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
147
+ kinto-20.3.0.dist-info/entry_points.txt,sha256=3KlqBWPKY81mrCe_oX0I5s1cRO7Q53nCLbnVr5P9LH4,85
148
+ kinto-20.3.0.dist-info/top_level.txt,sha256=EG_YmbZL6FAug9VwopG7JtF9SvH_r0DEnFp-3twPPys,6
149
+ kinto-20.3.0.dist-info/RECORD,,
File without changes