kinto 23.2.1__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.
Files changed (142) hide show
  1. kinto/__init__.py +92 -0
  2. kinto/__main__.py +249 -0
  3. kinto/authorization.py +134 -0
  4. kinto/config/__init__.py +94 -0
  5. kinto/config/kinto.tpl +270 -0
  6. kinto/contribute.json +27 -0
  7. kinto/core/__init__.py +246 -0
  8. kinto/core/authentication.py +48 -0
  9. kinto/core/authorization.py +311 -0
  10. kinto/core/cache/__init__.py +131 -0
  11. kinto/core/cache/memcached.py +112 -0
  12. kinto/core/cache/memory.py +104 -0
  13. kinto/core/cache/postgresql/__init__.py +178 -0
  14. kinto/core/cache/postgresql/schema.sql +23 -0
  15. kinto/core/cache/testing.py +208 -0
  16. kinto/core/cornice/__init__.py +93 -0
  17. kinto/core/cornice/cors.py +144 -0
  18. kinto/core/cornice/errors.py +40 -0
  19. kinto/core/cornice/pyramidhook.py +373 -0
  20. kinto/core/cornice/renderer.py +89 -0
  21. kinto/core/cornice/resource.py +205 -0
  22. kinto/core/cornice/service.py +641 -0
  23. kinto/core/cornice/util.py +138 -0
  24. kinto/core/cornice/validators/__init__.py +94 -0
  25. kinto/core/cornice/validators/_colander.py +142 -0
  26. kinto/core/cornice/validators/_marshmallow.py +182 -0
  27. kinto/core/cornice_swagger/__init__.py +92 -0
  28. kinto/core/cornice_swagger/converters/__init__.py +21 -0
  29. kinto/core/cornice_swagger/converters/exceptions.py +6 -0
  30. kinto/core/cornice_swagger/converters/parameters.py +90 -0
  31. kinto/core/cornice_swagger/converters/schema.py +249 -0
  32. kinto/core/cornice_swagger/swagger.py +725 -0
  33. kinto/core/cornice_swagger/templates/index.html +73 -0
  34. kinto/core/cornice_swagger/templates/index_script_template.html +21 -0
  35. kinto/core/cornice_swagger/util.py +42 -0
  36. kinto/core/cornice_swagger/views.py +78 -0
  37. kinto/core/decorators.py +74 -0
  38. kinto/core/errors.py +216 -0
  39. kinto/core/events.py +301 -0
  40. kinto/core/initialization.py +738 -0
  41. kinto/core/listeners/__init__.py +9 -0
  42. kinto/core/metrics.py +94 -0
  43. kinto/core/openapi.py +115 -0
  44. kinto/core/permission/__init__.py +202 -0
  45. kinto/core/permission/memory.py +167 -0
  46. kinto/core/permission/postgresql/__init__.py +489 -0
  47. kinto/core/permission/postgresql/migrations/migration_001_002.sql +18 -0
  48. kinto/core/permission/postgresql/schema.sql +41 -0
  49. kinto/core/permission/testing.py +487 -0
  50. kinto/core/resource/__init__.py +1311 -0
  51. kinto/core/resource/model.py +412 -0
  52. kinto/core/resource/schema.py +502 -0
  53. kinto/core/resource/viewset.py +230 -0
  54. kinto/core/schema.py +119 -0
  55. kinto/core/scripts.py +50 -0
  56. kinto/core/statsd.py +1 -0
  57. kinto/core/storage/__init__.py +436 -0
  58. kinto/core/storage/exceptions.py +53 -0
  59. kinto/core/storage/generators.py +58 -0
  60. kinto/core/storage/memory.py +651 -0
  61. kinto/core/storage/postgresql/__init__.py +1131 -0
  62. kinto/core/storage/postgresql/client.py +120 -0
  63. kinto/core/storage/postgresql/migrations/migration_001_002.sql +10 -0
  64. kinto/core/storage/postgresql/migrations/migration_002_003.sql +33 -0
  65. kinto/core/storage/postgresql/migrations/migration_003_004.sql +18 -0
  66. kinto/core/storage/postgresql/migrations/migration_004_005.sql +20 -0
  67. kinto/core/storage/postgresql/migrations/migration_005_006.sql +11 -0
  68. kinto/core/storage/postgresql/migrations/migration_006_007.sql +74 -0
  69. kinto/core/storage/postgresql/migrations/migration_007_008.sql +66 -0
  70. kinto/core/storage/postgresql/migrations/migration_008_009.sql +41 -0
  71. kinto/core/storage/postgresql/migrations/migration_009_010.sql +98 -0
  72. kinto/core/storage/postgresql/migrations/migration_010_011.sql +14 -0
  73. kinto/core/storage/postgresql/migrations/migration_011_012.sql +9 -0
  74. kinto/core/storage/postgresql/migrations/migration_012_013.sql +71 -0
  75. kinto/core/storage/postgresql/migrations/migration_013_014.sql +14 -0
  76. kinto/core/storage/postgresql/migrations/migration_014_015.sql +95 -0
  77. kinto/core/storage/postgresql/migrations/migration_015_016.sql +4 -0
  78. kinto/core/storage/postgresql/migrations/migration_016_017.sql +81 -0
  79. kinto/core/storage/postgresql/migrations/migration_017_018.sql +25 -0
  80. kinto/core/storage/postgresql/migrations/migration_018_019.sql +8 -0
  81. kinto/core/storage/postgresql/migrations/migration_019_020.sql +7 -0
  82. kinto/core/storage/postgresql/migrations/migration_020_021.sql +68 -0
  83. kinto/core/storage/postgresql/migrations/migration_021_022.sql +62 -0
  84. kinto/core/storage/postgresql/migrations/migration_022_023.sql +5 -0
  85. kinto/core/storage/postgresql/migrations/migration_023_024.sql +6 -0
  86. kinto/core/storage/postgresql/migrations/migration_024_025.sql +6 -0
  87. kinto/core/storage/postgresql/migrator.py +98 -0
  88. kinto/core/storage/postgresql/pool.py +55 -0
  89. kinto/core/storage/postgresql/schema.sql +143 -0
  90. kinto/core/storage/testing.py +1857 -0
  91. kinto/core/storage/utils.py +37 -0
  92. kinto/core/testing.py +182 -0
  93. kinto/core/utils.py +553 -0
  94. kinto/core/views/__init__.py +0 -0
  95. kinto/core/views/batch.py +163 -0
  96. kinto/core/views/errors.py +145 -0
  97. kinto/core/views/heartbeat.py +106 -0
  98. kinto/core/views/hello.py +69 -0
  99. kinto/core/views/openapi.py +35 -0
  100. kinto/core/views/version.py +50 -0
  101. kinto/events.py +3 -0
  102. kinto/plugins/__init__.py +0 -0
  103. kinto/plugins/accounts/__init__.py +94 -0
  104. kinto/plugins/accounts/authentication.py +63 -0
  105. kinto/plugins/accounts/scripts.py +61 -0
  106. kinto/plugins/accounts/utils.py +13 -0
  107. kinto/plugins/accounts/views.py +136 -0
  108. kinto/plugins/admin/README.md +3 -0
  109. kinto/plugins/admin/VERSION +1 -0
  110. kinto/plugins/admin/__init__.py +40 -0
  111. kinto/plugins/admin/build/VERSION +1 -0
  112. kinto/plugins/admin/build/assets/index-CYFwtKtL.css +6 -0
  113. kinto/plugins/admin/build/assets/index-DJ0m93zA.js +149 -0
  114. kinto/plugins/admin/build/assets/logo-VBRiKSPX.png +0 -0
  115. kinto/plugins/admin/build/index.html +18 -0
  116. kinto/plugins/admin/public/help.html +25 -0
  117. kinto/plugins/admin/views.py +42 -0
  118. kinto/plugins/default_bucket/__init__.py +191 -0
  119. kinto/plugins/flush.py +28 -0
  120. kinto/plugins/history/__init__.py +65 -0
  121. kinto/plugins/history/listener.py +181 -0
  122. kinto/plugins/history/views.py +66 -0
  123. kinto/plugins/openid/__init__.py +131 -0
  124. kinto/plugins/openid/utils.py +14 -0
  125. kinto/plugins/openid/views.py +193 -0
  126. kinto/plugins/prometheus.py +300 -0
  127. kinto/plugins/statsd.py +85 -0
  128. kinto/schema_validation.py +135 -0
  129. kinto/views/__init__.py +34 -0
  130. kinto/views/admin.py +195 -0
  131. kinto/views/buckets.py +45 -0
  132. kinto/views/collections.py +58 -0
  133. kinto/views/contribute.py +39 -0
  134. kinto/views/groups.py +90 -0
  135. kinto/views/permissions.py +235 -0
  136. kinto/views/records.py +133 -0
  137. kinto-23.2.1.dist-info/METADATA +232 -0
  138. kinto-23.2.1.dist-info/RECORD +142 -0
  139. kinto-23.2.1.dist-info/WHEEL +5 -0
  140. kinto-23.2.1.dist-info/entry_points.txt +5 -0
  141. kinto-23.2.1.dist-info/licenses/LICENSE +13 -0
  142. kinto-23.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,436 @@
1
+ import logging
2
+ import random
3
+ import warnings
4
+ from collections import namedtuple
5
+
6
+ from pyramid.settings import asbool
7
+
8
+ from kinto.core.decorators import deprecate_kwargs
9
+
10
+ from . import generators
11
+
12
+
13
+ class Missing:
14
+ """Dummy value to represent a value that is completely absent from an object.
15
+
16
+ Handling these correctly is important for pagination.
17
+ """
18
+
19
+ pass
20
+
21
+
22
+ MISSING = Missing()
23
+
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ Filter = namedtuple("Filter", ["field", "value", "operator"])
29
+ """Filtering properties."""
30
+
31
+ Sort = namedtuple("Sort", ["field", "direction"])
32
+ """Sorting properties."""
33
+
34
+ DEFAULT_ID_FIELD = "id"
35
+ DEFAULT_MODIFIED_FIELD = "last_modified"
36
+ DEFAULT_DELETED_FIELD = "deleted"
37
+
38
+ _HEARTBEAT_DELETE_RATE = 0.6
39
+ _HEARTBEAT_RESOURCE_NAME = "__heartbeat__"
40
+ _HEART_PARENT_ID = _HEARTBEAT_RESOURCE_NAME
41
+ _HEARTBEAT_OBJECT = {"__heartbeat__": True}
42
+
43
+
44
+ class StorageBase:
45
+ """Storage abstraction used by resource views.
46
+
47
+ It is meant to be instantiated at application startup.
48
+ Any operation may raise a `HTTPServiceUnavailable` error if an error
49
+ occurs with the underlying service.
50
+
51
+ Configuration can be changed to choose which storage backend will
52
+ persist the objects.
53
+
54
+ :raises: :exc:`~pyramid:pyramid.httpexceptions.HTTPServiceUnavailable`
55
+ """
56
+
57
+ id_generator = generators.UUID4()
58
+ """Id generator used when no one is provided for create."""
59
+
60
+ def initialize_schema(self, dry_run=False):
61
+ """Create every necessary objects (like tables or indices) in the
62
+ backend.
63
+
64
+ This is executed when the ``kinto migrate`` command is run.
65
+
66
+ :param bool dry_run: simulate instead of executing the operations.
67
+ """
68
+ raise NotImplementedError
69
+
70
+ def flush(self):
71
+ """Remove **every** object from this storage."""
72
+ raise NotImplementedError
73
+
74
+ def resource_timestamp(self, resource_name, parent_id):
75
+ """Get the highest timestamp of every objects in this `resource_name` for
76
+ this `parent_id`.
77
+
78
+ .. note::
79
+
80
+ This should take deleted objects into account.
81
+
82
+ :param str resource_name: the resource name.
83
+ :param str parent_id: the resource parent.
84
+
85
+ :returns: the latest timestamp of the resource.
86
+ :rtype: int
87
+ """
88
+ raise NotImplementedError
89
+
90
+ def all_resources_timestamps(self, resource_name):
91
+ """Get the highest timestamp of every objects in this `resource_name` for
92
+ each `parent_id`.
93
+
94
+ .. note::
95
+
96
+ This should take deleted objects into account.
97
+
98
+ :param str resource_name: the resource name.
99
+
100
+ :returns: the latest timestamp of the resource by `parent_id`.
101
+ :rtype: dict[str, int]
102
+ """
103
+ raise NotImplementedError
104
+
105
+ def create(
106
+ self,
107
+ resource_name,
108
+ parent_id,
109
+ obj,
110
+ id_generator=None,
111
+ id_field=DEFAULT_ID_FIELD,
112
+ modified_field=DEFAULT_MODIFIED_FIELD,
113
+ ):
114
+ """Create the specified `obj` in this `resource_name` for this `parent_id`.
115
+ Assign the id to the object, using the attribute
116
+ :attr:`kinto.core.resource.model.Model.id_field`.
117
+
118
+ .. note::
119
+
120
+ This will update the resource timestamp.
121
+
122
+ :raises: :exc:`kinto.core.storage.exceptions.UnicityError`
123
+
124
+ :param str resource_name: the resource name.
125
+ :param str parent_id: the resource parent.
126
+ :param dict obj: the object to create.
127
+
128
+ :returns: the newly created object.
129
+ :rtype: dict
130
+ """
131
+ raise NotImplementedError
132
+
133
+ def get(
134
+ self,
135
+ resource_name,
136
+ parent_id,
137
+ object_id,
138
+ id_field=DEFAULT_ID_FIELD,
139
+ modified_field=DEFAULT_MODIFIED_FIELD,
140
+ ):
141
+ """Retrieve the object with specified `object_id`, or raise error
142
+ if not found.
143
+
144
+ :raises: :exc:`kinto.core.storage.exceptions.ObjectNotFoundError`
145
+
146
+ :param str resource_name: the resource name.
147
+ :param str parent_id: the resource parent.
148
+
149
+ :param str object_id: unique identifier of the object
150
+
151
+ :returns: the stored object.
152
+ :rtype: dict
153
+ """
154
+ raise NotImplementedError
155
+
156
+ def update(
157
+ self,
158
+ resource_name,
159
+ parent_id,
160
+ object_id,
161
+ obj,
162
+ id_field=DEFAULT_ID_FIELD,
163
+ modified_field=DEFAULT_MODIFIED_FIELD,
164
+ ):
165
+ """Overwrite the `obj` with the specified `object_id`.
166
+
167
+ If the specified id is not found, the object is created with the
168
+ specified id.
169
+
170
+ .. note::
171
+
172
+ This will update the resource timestamp.
173
+
174
+ :param str resource_name: the resource name.
175
+ :param str parent_id: the resource parent.
176
+ :param str object_id: unique identifier of the object
177
+ :param dict obj: the object to update or create.
178
+
179
+ :returns: the updated object.
180
+ :rtype: dict
181
+ """
182
+ raise NotImplementedError
183
+
184
+ def delete(
185
+ self,
186
+ resource_name,
187
+ parent_id,
188
+ object_id,
189
+ id_field=DEFAULT_ID_FIELD,
190
+ with_deleted=True,
191
+ modified_field=DEFAULT_MODIFIED_FIELD,
192
+ deleted_field=DEFAULT_DELETED_FIELD,
193
+ last_modified=None,
194
+ ):
195
+ """Delete the object with specified `object_id`, and raise error
196
+ if not found.
197
+
198
+ Deleted objects must be removed from the database, but their ids and
199
+ timestamps of deletion must be tracked for synchronization purposes.
200
+ (See :meth:`kinto.core.storage.StorageBase.get_all`)
201
+
202
+ .. note::
203
+
204
+ This will update the resource timestamp.
205
+
206
+ :raises: :exc:`kinto.core.storage.exceptions.ObjectNotFoundError`
207
+
208
+ :param str resource_name: the resource name.
209
+ :param str parent_id: the resource parent.
210
+
211
+ :param str object_id: unique identifier of the object
212
+ :param bool with_deleted: track deleted object with a tombstone
213
+
214
+ :returns: the deleted object, with minimal set of attributes.
215
+ :rtype: dict
216
+ """
217
+ raise NotImplementedError
218
+
219
+ def delete_all(
220
+ self,
221
+ resource_name,
222
+ parent_id,
223
+ filters=None,
224
+ sorting=None,
225
+ pagination_rules=None,
226
+ limit=None,
227
+ id_field=DEFAULT_ID_FIELD,
228
+ with_deleted=True,
229
+ modified_field=DEFAULT_MODIFIED_FIELD,
230
+ deleted_field=DEFAULT_DELETED_FIELD,
231
+ ):
232
+ """Delete all objects in this `resource_name` for this `parent_id`.
233
+
234
+ :param str resource_name: the resource name.
235
+ :param str parent_id: the resource parent.
236
+
237
+ :param filters: Optionnally filter the objects to delete.
238
+ :type filters: list of :class:`kinto.core.storage.Filter`
239
+ :param sorting: Optionnally sort the objects by attribute.
240
+ Each sort instruction in this list refers to a field and a
241
+ direction (negative means descending). All sort instructions are
242
+ cumulative.
243
+ :type sorting: list of :class:`kinto.core.storage.Sort`
244
+
245
+ :param pagination_rules: Optionnally paginate the deletion of objects.
246
+ This list of rules aims to reduce the set of objects to the current
247
+ page. A rule is a list of filters (see `filters` parameter),
248
+ and all rules are combined using *OR*.
249
+ :type pagination_rules: list of list of
250
+ :class:`kinto.core.storage.Filter`
251
+
252
+ :param int limit: Optionnally limit the number of objects to be
253
+ deleted.
254
+
255
+ :param bool with_deleted: track deleted objects with a tombstone
256
+
257
+ :returns: the list of deleted objects, with minimal set of attributes.
258
+ :rtype: list
259
+ """
260
+ raise NotImplementedError
261
+
262
+ def purge_deleted(
263
+ self,
264
+ resource_name,
265
+ parent_id,
266
+ before=None,
267
+ max_retained=None,
268
+ id_field=DEFAULT_ID_FIELD,
269
+ modified_field=DEFAULT_MODIFIED_FIELD,
270
+ ):
271
+ """Delete all deleted object tombstones in this `resource_name`
272
+ for this `parent_id`.
273
+
274
+ :param str resource_name: the resource name.
275
+ :param str parent_id: the resource parent.
276
+
277
+ :param int before: Optional timestamp to limit deletion (exclusive).
278
+ :param int max_count: Optional maximum of tombstones to keep per collection.
279
+
280
+ :returns: The number of deleted objects.
281
+ :rtype: int
282
+
283
+ """
284
+ raise NotImplementedError
285
+
286
+ @deprecate_kwargs({"collection_id": "resource_name"})
287
+ def get_all(self, *args, **kwargs):
288
+ """Legacy method to support code that relied on the old API where the storage's
289
+ get_all() would return a tuple of (<list of objects paginated>, <count of all>).
290
+ Since then, we're being more explicit and expecting the client to deliberately
291
+ decide if they need a paginated list or a count.
292
+
293
+ This method exists solely to make the transition easier.
294
+ """
295
+ warnings.warn("Use either self.list_all() or self.count_all()", DeprecationWarning)
296
+ list_ = self.list_all(*args, **kwargs)
297
+ kwargs.pop("pagination_rules", None)
298
+ kwargs.pop("limit", None)
299
+ kwargs.pop("sorting", None)
300
+ kwargs.pop("include_deleted", None)
301
+ count = self.count_all(*args, **kwargs)
302
+ return (list_, count)
303
+
304
+ def list_all(
305
+ self,
306
+ resource_name,
307
+ parent_id,
308
+ filters=None,
309
+ sorting=None,
310
+ pagination_rules=None,
311
+ limit=None,
312
+ include_deleted=False,
313
+ id_field=DEFAULT_ID_FIELD,
314
+ modified_field=DEFAULT_MODIFIED_FIELD,
315
+ deleted_field=DEFAULT_DELETED_FIELD,
316
+ ):
317
+ """Retrieve all objects in this `resource_name` for this `parent_id`.
318
+
319
+ :param str resource_name: the resource name.
320
+
321
+ :param str parent_id: the resource parent, possibly
322
+ containing a wildcard '*'. (This can happen when
323
+ implementing "administrator" operations on a Resource,
324
+ for example, like ``kinto.plugins.accounts``.)
325
+
326
+ :param filters: Optionally filter the objects by their attribute.
327
+ Each filter in this list is a tuple of a field, a value and a
328
+ comparison (see `kinto.core.utils.COMPARISON`). All filters
329
+ are combined using *AND*.
330
+ :type filters: list of :class:`kinto.core.storage.Filter`
331
+
332
+ :param sorting: Optionnally sort the objects by attribute.
333
+ Each sort instruction in this list refers to a field and a
334
+ direction (negative means descending). All sort instructions are
335
+ cumulative.
336
+ :type sorting: list of :class:`kinto.core.storage.Sort`
337
+
338
+ :param pagination_rules: Optionnally paginate the list of objects.
339
+ This list of rules aims to reduce the set of objects to the current
340
+ page. A rule is a list of filters (see `filters` parameter),
341
+ and all rules are combined using *OR*.
342
+ :type pagination_rules: list of list of
343
+ :class:`kinto.core.storage.Filter`
344
+
345
+ :param int limit: Optionnally limit the number of objects to be
346
+ retrieved.
347
+
348
+ :param bool include_deleted: Optionnally include the deleted objects
349
+ that match the filters.
350
+
351
+ :returns: the limited list of objects of
352
+ matching objects in the resource (deleted ones excluded).
353
+ :rtype: list
354
+ """
355
+ raise NotImplementedError
356
+
357
+ def count_all(
358
+ self,
359
+ resource_name,
360
+ parent_id,
361
+ filters=None,
362
+ id_field=DEFAULT_ID_FIELD,
363
+ modified_field=DEFAULT_MODIFIED_FIELD,
364
+ deleted_field=DEFAULT_DELETED_FIELD,
365
+ ):
366
+ """Return a count of all objects in this `resource_name` for this `parent_id`.
367
+
368
+ :param str resource_name: the resource name.
369
+ :param str parent_id: the parent resource, possibly
370
+ containing a wildcard '*'. (This can happen when
371
+ implementing "administrator" operations on a UserResource,
372
+ for example.)
373
+ :param filters: Optionally filter the objects by their attribute.
374
+ Each filter in this list is a tuple of a field, a value and a
375
+ comparison (see `kinto.core.utils.COMPARISON`). All filters
376
+ are combined using *AND*.
377
+ :type filters: list of :class:`kinto.core.storage.Filter`
378
+ :returns: the total number of matching objects in the resource (deleted ones excluded).
379
+ :rtype: int
380
+ """
381
+ raise NotImplementedError
382
+
383
+ def collection_timestamp(self, collection_id, parent_id):
384
+ message = "`collection_timestamp()` is deprecated, use `resource_timestamp()` instead."
385
+ warnings.warn(message, DeprecationWarning)
386
+ return self.resource_timestamp(resource_name=collection_id, parent_id=parent_id)
387
+
388
+ def trim_objects(
389
+ self,
390
+ resource_name: str,
391
+ parent_id: str,
392
+ filters: list,
393
+ max_objects: int,
394
+ id_field: str = DEFAULT_ID_FIELD,
395
+ modified_field: str = DEFAULT_MODIFIED_FIELD,
396
+ ) -> int:
397
+ """
398
+ Trim the last N objects in the specified resource matching the filters.
399
+
400
+ :param str resource_name: the resource name.
401
+ :param str parent_id: the resource parent.
402
+ :param list filters: list of :class:`kinto.core.storage.Filter` to filter objects.
403
+ :param int max_objects: maximum number of objects to keep.
404
+ :param str id_field: the id field name.
405
+ :param str modified_field: the modified field name.
406
+ :returns: the number of deleted objects.
407
+ :rtype: int"""
408
+ raise NotImplementedError
409
+
410
+
411
+ def heartbeat(backend):
412
+ def ping(request):
413
+ """Test that storage is operational.
414
+
415
+ :param request: current request object
416
+ :type request: :class:`~pyramid:pyramid.request.Request`
417
+ :returns: ``True`` is everything is ok, ``False`` otherwise.
418
+ :rtype: bool
419
+ """
420
+ try:
421
+ storage_kw = dict(resource_name=_HEARTBEAT_RESOURCE_NAME, parent_id=_HEART_PARENT_ID)
422
+ if asbool(request.registry.settings.get("readonly")):
423
+ # Do not try to write in readonly mode.
424
+ backend.get_all(**storage_kw)
425
+ else:
426
+ if random.SystemRandom().random() < _HEARTBEAT_DELETE_RATE:
427
+ backend.delete_all(**storage_kw)
428
+ backend.purge_deleted(**storage_kw) # Kinto/kinto#985
429
+ else:
430
+ backend.create(obj=_HEARTBEAT_OBJECT, **storage_kw)
431
+ return True
432
+ except Exception:
433
+ logger.exception("Heartbeat Error")
434
+ return False
435
+
436
+ return ping
@@ -0,0 +1,53 @@
1
+ """Exceptions raised by storage backend."""
2
+
3
+
4
+ class BackendError(Exception):
5
+ """A generic exception raised by storage on error.
6
+
7
+ :param Exception original: the wrapped exception raised by underlying
8
+ library.
9
+ """
10
+
11
+ def __init__(self, original=None, message=None, *args, **kwargs):
12
+ self.original = original
13
+ if message is None:
14
+ message = f"{original.__class__.__name__}: {original}"
15
+ super().__init__(message, *args, **kwargs)
16
+
17
+
18
+ class ReadonlyError(BackendError):
19
+ """An error raised when a write operation is attempted on a
20
+ read-only instance.
21
+ """
22
+
23
+ pass
24
+
25
+
26
+ class RecordNotFoundError(Exception):
27
+ """Deprecated exception name."""
28
+
29
+ pass
30
+
31
+
32
+ class ObjectNotFoundError(RecordNotFoundError):
33
+ """An exception raised when a specific object could not be found."""
34
+
35
+ pass
36
+
37
+
38
+ class IntegrityError(BackendError):
39
+ pass
40
+
41
+
42
+ class UnicityError(IntegrityError):
43
+ """An exception raised on unicity constraint violation.
44
+
45
+ Raised by storage backend when the creation or the modification of a
46
+ object violates the unicity constraints defined by the resource.
47
+
48
+ """
49
+
50
+ def __init__(self, field, *args, **kwargs):
51
+ self.field = field
52
+ self.msg = f"{field} is not unique"
53
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,58 @@
1
+ import re
2
+ from uuid import uuid4
3
+
4
+
5
+ class Generator:
6
+ """Base generator for objects ids.
7
+
8
+ Id generators are used by storage backend during object creation, and at
9
+ resource level to validate object id in requests paths.
10
+ """
11
+
12
+ regexp = r"^[a-zA-Z0-9][a-zA-Z0-9_-]*$"
13
+ """Default object id pattern. Can be changed to comply with custom ids."""
14
+
15
+ def __init__(self, config=None):
16
+ self.config = config
17
+ self._regexp = None
18
+
19
+ if not self.match(self()):
20
+ error_msg = "Generated object id does comply with regexp."
21
+ raise ValueError(error_msg)
22
+
23
+ def match(self, object_id):
24
+ """Validate that object ids match the generator. This is used mainly
25
+ when an object id is picked arbitrarily (e.g with ``PUT`` requests).
26
+
27
+ :returns: `True` if the specified object id matches expected format.
28
+ :rtype: bool
29
+ """
30
+ if self._regexp is None:
31
+ self._regexp = re.compile(self.regexp)
32
+ return self._regexp.match(object_id)
33
+
34
+ def __call__(self):
35
+ """
36
+ :returns: A object id, most likely unique.
37
+ :rtype: str
38
+ """
39
+ raise NotImplementedError
40
+
41
+
42
+ class UUID4(Generator):
43
+ """UUID4 object id generator.
44
+
45
+ UUID block are separated with ``-``.
46
+ (example: ``'472be9ec-26fe-461b-8282-9c4e4b207ab3'``)
47
+
48
+ UUIDs are very safe in term of unicity. If 1 billion of UUIDs are generated
49
+ every second for the next 100 years, the probability of creating just one
50
+ duplicate would be about 50% (`source <http://en.wikipedia.org/wiki/\
51
+ Universally_unique_identifier#Random_UUID_probability_of_duplicates>`_).
52
+ """
53
+
54
+ regexp = r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-" r"[0-9a-f]{4}-[0-9a-f]{12}$"
55
+ """UUID4 accurate pattern."""
56
+
57
+ def __call__(self):
58
+ return str(uuid4())