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.
- kinto/__init__.py +92 -0
- kinto/__main__.py +249 -0
- kinto/authorization.py +134 -0
- kinto/config/__init__.py +94 -0
- kinto/config/kinto.tpl +270 -0
- kinto/contribute.json +27 -0
- kinto/core/__init__.py +246 -0
- kinto/core/authentication.py +48 -0
- kinto/core/authorization.py +311 -0
- kinto/core/cache/__init__.py +131 -0
- kinto/core/cache/memcached.py +112 -0
- kinto/core/cache/memory.py +104 -0
- kinto/core/cache/postgresql/__init__.py +178 -0
- kinto/core/cache/postgresql/schema.sql +23 -0
- kinto/core/cache/testing.py +208 -0
- kinto/core/cornice/__init__.py +93 -0
- kinto/core/cornice/cors.py +144 -0
- kinto/core/cornice/errors.py +40 -0
- kinto/core/cornice/pyramidhook.py +373 -0
- kinto/core/cornice/renderer.py +89 -0
- kinto/core/cornice/resource.py +205 -0
- kinto/core/cornice/service.py +641 -0
- kinto/core/cornice/util.py +138 -0
- kinto/core/cornice/validators/__init__.py +94 -0
- kinto/core/cornice/validators/_colander.py +142 -0
- kinto/core/cornice/validators/_marshmallow.py +182 -0
- kinto/core/cornice_swagger/__init__.py +92 -0
- kinto/core/cornice_swagger/converters/__init__.py +21 -0
- kinto/core/cornice_swagger/converters/exceptions.py +6 -0
- kinto/core/cornice_swagger/converters/parameters.py +90 -0
- kinto/core/cornice_swagger/converters/schema.py +249 -0
- kinto/core/cornice_swagger/swagger.py +725 -0
- kinto/core/cornice_swagger/templates/index.html +73 -0
- kinto/core/cornice_swagger/templates/index_script_template.html +21 -0
- kinto/core/cornice_swagger/util.py +42 -0
- kinto/core/cornice_swagger/views.py +78 -0
- kinto/core/decorators.py +74 -0
- kinto/core/errors.py +216 -0
- kinto/core/events.py +301 -0
- kinto/core/initialization.py +738 -0
- kinto/core/listeners/__init__.py +9 -0
- kinto/core/metrics.py +94 -0
- kinto/core/openapi.py +115 -0
- kinto/core/permission/__init__.py +202 -0
- kinto/core/permission/memory.py +167 -0
- kinto/core/permission/postgresql/__init__.py +489 -0
- kinto/core/permission/postgresql/migrations/migration_001_002.sql +18 -0
- kinto/core/permission/postgresql/schema.sql +41 -0
- kinto/core/permission/testing.py +487 -0
- kinto/core/resource/__init__.py +1311 -0
- kinto/core/resource/model.py +412 -0
- kinto/core/resource/schema.py +502 -0
- kinto/core/resource/viewset.py +230 -0
- kinto/core/schema.py +119 -0
- kinto/core/scripts.py +50 -0
- kinto/core/statsd.py +1 -0
- kinto/core/storage/__init__.py +436 -0
- kinto/core/storage/exceptions.py +53 -0
- kinto/core/storage/generators.py +58 -0
- kinto/core/storage/memory.py +651 -0
- kinto/core/storage/postgresql/__init__.py +1131 -0
- kinto/core/storage/postgresql/client.py +120 -0
- kinto/core/storage/postgresql/migrations/migration_001_002.sql +10 -0
- kinto/core/storage/postgresql/migrations/migration_002_003.sql +33 -0
- kinto/core/storage/postgresql/migrations/migration_003_004.sql +18 -0
- kinto/core/storage/postgresql/migrations/migration_004_005.sql +20 -0
- kinto/core/storage/postgresql/migrations/migration_005_006.sql +11 -0
- kinto/core/storage/postgresql/migrations/migration_006_007.sql +74 -0
- kinto/core/storage/postgresql/migrations/migration_007_008.sql +66 -0
- kinto/core/storage/postgresql/migrations/migration_008_009.sql +41 -0
- kinto/core/storage/postgresql/migrations/migration_009_010.sql +98 -0
- kinto/core/storage/postgresql/migrations/migration_010_011.sql +14 -0
- kinto/core/storage/postgresql/migrations/migration_011_012.sql +9 -0
- kinto/core/storage/postgresql/migrations/migration_012_013.sql +71 -0
- kinto/core/storage/postgresql/migrations/migration_013_014.sql +14 -0
- kinto/core/storage/postgresql/migrations/migration_014_015.sql +95 -0
- kinto/core/storage/postgresql/migrations/migration_015_016.sql +4 -0
- kinto/core/storage/postgresql/migrations/migration_016_017.sql +81 -0
- kinto/core/storage/postgresql/migrations/migration_017_018.sql +25 -0
- kinto/core/storage/postgresql/migrations/migration_018_019.sql +8 -0
- kinto/core/storage/postgresql/migrations/migration_019_020.sql +7 -0
- kinto/core/storage/postgresql/migrations/migration_020_021.sql +68 -0
- kinto/core/storage/postgresql/migrations/migration_021_022.sql +62 -0
- kinto/core/storage/postgresql/migrations/migration_022_023.sql +5 -0
- kinto/core/storage/postgresql/migrations/migration_023_024.sql +6 -0
- kinto/core/storage/postgresql/migrations/migration_024_025.sql +6 -0
- kinto/core/storage/postgresql/migrator.py +98 -0
- kinto/core/storage/postgresql/pool.py +55 -0
- kinto/core/storage/postgresql/schema.sql +143 -0
- kinto/core/storage/testing.py +1857 -0
- kinto/core/storage/utils.py +37 -0
- kinto/core/testing.py +182 -0
- kinto/core/utils.py +553 -0
- kinto/core/views/__init__.py +0 -0
- kinto/core/views/batch.py +163 -0
- kinto/core/views/errors.py +145 -0
- kinto/core/views/heartbeat.py +106 -0
- kinto/core/views/hello.py +69 -0
- kinto/core/views/openapi.py +35 -0
- kinto/core/views/version.py +50 -0
- kinto/events.py +3 -0
- kinto/plugins/__init__.py +0 -0
- kinto/plugins/accounts/__init__.py +94 -0
- kinto/plugins/accounts/authentication.py +63 -0
- kinto/plugins/accounts/scripts.py +61 -0
- kinto/plugins/accounts/utils.py +13 -0
- kinto/plugins/accounts/views.py +136 -0
- kinto/plugins/admin/README.md +3 -0
- kinto/plugins/admin/VERSION +1 -0
- kinto/plugins/admin/__init__.py +40 -0
- kinto/plugins/admin/build/VERSION +1 -0
- kinto/plugins/admin/build/assets/index-CYFwtKtL.css +6 -0
- kinto/plugins/admin/build/assets/index-DJ0m93zA.js +149 -0
- kinto/plugins/admin/build/assets/logo-VBRiKSPX.png +0 -0
- kinto/plugins/admin/build/index.html +18 -0
- kinto/plugins/admin/public/help.html +25 -0
- kinto/plugins/admin/views.py +42 -0
- kinto/plugins/default_bucket/__init__.py +191 -0
- kinto/plugins/flush.py +28 -0
- kinto/plugins/history/__init__.py +65 -0
- kinto/plugins/history/listener.py +181 -0
- kinto/plugins/history/views.py +66 -0
- kinto/plugins/openid/__init__.py +131 -0
- kinto/plugins/openid/utils.py +14 -0
- kinto/plugins/openid/views.py +193 -0
- kinto/plugins/prometheus.py +300 -0
- kinto/plugins/statsd.py +85 -0
- kinto/schema_validation.py +135 -0
- kinto/views/__init__.py +34 -0
- kinto/views/admin.py +195 -0
- kinto/views/buckets.py +45 -0
- kinto/views/collections.py +58 -0
- kinto/views/contribute.py +39 -0
- kinto/views/groups.py +90 -0
- kinto/views/permissions.py +235 -0
- kinto/views/records.py +133 -0
- kinto-23.2.1.dist-info/METADATA +232 -0
- kinto-23.2.1.dist-info/RECORD +142 -0
- kinto-23.2.1.dist-info/WHEEL +5 -0
- kinto-23.2.1.dist-info/entry_points.txt +5 -0
- kinto-23.2.1.dist-info/licenses/LICENSE +13 -0
- 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())
|