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
kinto/core/events.py ADDED
@@ -0,0 +1,301 @@
1
+ import logging
2
+ import warnings
3
+ from collections import OrderedDict
4
+ from enum import Enum
5
+
6
+ import pyramid.tweens
7
+ import transaction
8
+ from pyramid.events import NewRequest
9
+
10
+ from kinto.core.utils import strip_uri_prefix
11
+
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class ACTIONS(Enum):
17
+ CREATE = "create"
18
+ DELETE = "delete"
19
+ READ = "read"
20
+ UPDATE = "update"
21
+
22
+ @staticmethod
23
+ def from_string_list(elements):
24
+ return tuple(ACTIONS(el) for el in elements)
25
+
26
+
27
+ class _ResourceEvent:
28
+ def __init__(self, payload, request):
29
+ self.payload = payload
30
+ self.request = request
31
+
32
+ def __repr__(self):
33
+ return f"<{self.__class__.__name__} action={self.payload['action']} uri={self.payload['uri']}>"
34
+
35
+ @property
36
+ def read_records(self):
37
+ message = "`read_records` is deprecated, use `read_objects` instead."
38
+ warnings.warn(message, DeprecationWarning)
39
+ return self.read_objects
40
+
41
+ @property
42
+ def impacted_records(self):
43
+ message = "`impacted_records` is deprecated, use `impacted_objects` instead."
44
+ warnings.warn(message, DeprecationWarning)
45
+ return self.impacted_objects
46
+
47
+
48
+ class ResourceRead(_ResourceEvent):
49
+ """Triggered when a resource is being read."""
50
+
51
+ def __init__(self, payload, read_objects, request):
52
+ super().__init__(payload, request)
53
+ self.read_objects = read_objects
54
+
55
+
56
+ class ResourceChanged(_ResourceEvent):
57
+ """Triggered when a resource is being changed."""
58
+
59
+ def __init__(self, payload, impacted_objects, request):
60
+ super().__init__(payload, request)
61
+ self.impacted_objects = impacted_objects
62
+
63
+
64
+ class AfterResourceRead(_ResourceEvent):
65
+ """Triggered after a resource was successfully read."""
66
+
67
+ def __init__(self, payload, read_objects, request):
68
+ super().__init__(payload, request)
69
+ self.read_objects = read_objects
70
+
71
+
72
+ class AfterResourceChanged(_ResourceEvent):
73
+ """Triggered after a resource was successfully changed."""
74
+
75
+ def __init__(self, payload, impacted_objects, request):
76
+ super().__init__(payload, request)
77
+ self.impacted_objects = impacted_objects
78
+
79
+
80
+ class EventCollector(object):
81
+ """A collection to gather events emitted over the course of a request.
82
+
83
+ Events are gathered by parent id, resource type, and event
84
+ type. This serves as a primitive normalization so that we can emit
85
+ fewer events.
86
+ """
87
+
88
+ def __init__(self, cascade_level=1):
89
+ self.cascade_level = cascade_level
90
+ """Current level of event cascade. When we start consuming the
91
+ gathered events, we increment it. This way, events emitted from
92
+ events listeners (cascade) are not merged with upstream ones.
93
+ """
94
+
95
+ self.event_dict = OrderedDict()
96
+ """The events as collected so far.
97
+
98
+ The key of the event_dict is a quadruple (cascade_level, resource_name,
99
+ parent_id, action). The value is a triple (impacted, request,
100
+ payload). If the same (cascade_level, resource_name, parent_id, action) is
101
+ encountered, we just extend the existing impacted with the new
102
+ impacted. N.B. this means all values in the payload must not
103
+ be specific to a single impacted_object. See
104
+ https://github.com/Kinto/kinto/issues/945 and
105
+ https://github.com/Kinto/kinto/issues/1731.
106
+ """
107
+
108
+ def add_event(self, resource_name, parent_id, action, payload, impacted, request):
109
+ key = (self.cascade_level, resource_name, parent_id, action)
110
+ if key not in self.event_dict:
111
+ value = (payload, impacted, request)
112
+ self.event_dict[key] = value
113
+ else:
114
+ old_value = self.event_dict[key]
115
+ (old_payload, old_impacted, old_request) = old_value
116
+ # May be a good idea to assert that old_payload == payload here.
117
+ self.event_dict[key] = (old_payload, old_impacted + impacted, old_request)
118
+
119
+ def drain(self):
120
+ """Return an iterator that removes elements from this EventCollector.
121
+
122
+ This can be used to process events while still allowing events
123
+ to be added (for instance, as part of a cascade where events
124
+ add other events).
125
+
126
+ Items yielded will be of a tuple suitable for using as
127
+ arguments to EventCollector.add_event.
128
+ """
129
+ # Since we start consuming the gathered events, we increment the cascade level.
130
+ self.cascade_level += 1
131
+ return EventCollectorDrain(self)
132
+
133
+
134
+ class EventCollectorDrain(object):
135
+ """An iterator that drains an EventCollector.
136
+
137
+ Get one using EventCollector.drain()."""
138
+
139
+ def __init__(self, event_collector):
140
+ self.event_collector = event_collector
141
+
142
+ def __iter__(self):
143
+ return self
144
+
145
+ def __next__(self):
146
+ if self.event_collector.event_dict:
147
+ # Get the "first" key in insertion order, so as to process
148
+ # events in the same order they were queued.
149
+ key = next(iter(self.event_collector.event_dict.keys()))
150
+ value = self.event_collector.event_dict.pop(key)
151
+ return key + value
152
+ else:
153
+ raise StopIteration
154
+
155
+
156
+ def notify_resource_events_before(handler, registry):
157
+ """Tween that runs ResourceChanged events.
158
+
159
+ This tween runs under EXCVIEW, so exceptions raised by
160
+ ResourceChanged events are caught and dealt the same as any other
161
+ exceptions.
162
+
163
+ """
164
+
165
+ def tween(request):
166
+ response = handler(request)
167
+ for event in request.get_resource_events():
168
+ request.registry.notify(event)
169
+
170
+ return response
171
+
172
+ return tween
173
+
174
+
175
+ def setup_transaction_hook(config):
176
+ """
177
+ Resource events are plugged with the transactions of ``pyramid_tm``.
178
+
179
+ Once a transaction is committed, ``AfterResourceRead`` and
180
+ ``AfterResourceChanged`` events are sent.
181
+ """
182
+
183
+ def _notify_resource_events_after(success, request):
184
+ """Notify the accumulated resource events if transaction succeeds."""
185
+ if not success: # pragma: no cover
186
+ return
187
+
188
+ for event in request.get_resource_events(after_commit=True):
189
+ try:
190
+ request.registry.notify(event)
191
+ except Exception:
192
+ logger.error("Unable to notify", exc_info=True)
193
+
194
+ def on_new_request(event):
195
+ """When a new request comes in, hook on transaction commit."""
196
+ # Since there is one transaction per batch, ignore subrequests.
197
+ if hasattr(event.request, "parent"):
198
+ return
199
+ current = transaction.get()
200
+ current.addAfterCommitHook(_notify_resource_events_after, args=(event.request,))
201
+
202
+ config.add_subscriber(on_new_request, NewRequest)
203
+ config.add_tween(
204
+ "kinto.core.events.notify_resource_events_before", under=pyramid.tweens.EXCVIEW
205
+ )
206
+
207
+
208
+ def get_resource_events(request, after_commit=False):
209
+ """Generator to iterate the list of events triggered on resources.
210
+
211
+ The list is sorted chronologically (see OrderedDict).
212
+
213
+ This drains the resource_events currently in the request, which
214
+ allows us to process new events as they are added by current
215
+ events. However, once the iteration is over, we merge all the
216
+ events we've emitted into a new resource_events, which we store on
217
+ the request so we can reprocess the same events in an after-commit
218
+ tween.
219
+
220
+ This generator must be completely consumed!
221
+ """
222
+ by_resource = request.bound_data.get("resource_events", EventCollector())
223
+ afterwards = EventCollector()
224
+
225
+ for event in by_resource.drain():
226
+ (_, resource_name, parent_id, action, payload, impacted, request) = event
227
+ afterwards.add_event(resource_name, parent_id, action, payload, impacted, request)
228
+
229
+ if after_commit:
230
+ if action == ACTIONS.READ:
231
+ event_cls = AfterResourceRead
232
+ else:
233
+ event_cls = AfterResourceChanged
234
+ else:
235
+ if action == ACTIONS.READ:
236
+ event_cls = ResourceRead
237
+ else:
238
+ event_cls = ResourceChanged
239
+
240
+ yield event_cls(payload, impacted, request)
241
+
242
+ request.bound_data["resource_events"] = afterwards
243
+
244
+
245
+ def notify_resource_event(
246
+ request, parent_id, timestamp, data, action, old=None, resource_name=None, resource_data=None
247
+ ):
248
+ """Request helper to stack a resource event.
249
+
250
+ If a similar event (same resource, same action) already occured during the
251
+ current transaction (e.g. batch) then just extend the impacted objects of
252
+ the previous one.
253
+
254
+ :param resource_name: The name of the resource on which the event
255
+ happened (taken from the request if not provided).
256
+ :param resource_data: Information about the resource on which the
257
+ event is being emitted. Usually contains information about how
258
+ to find this object in the hierarchy (for instance,
259
+ ``bucket_id`` and ``collection_id`` for a record). Taken from
260
+ the request matchdict if absent.
261
+ :type resource_data: dict
262
+
263
+ """
264
+ if action == ACTIONS.READ:
265
+ if not isinstance(data, list):
266
+ data = [data]
267
+ impacted = data
268
+ elif action == ACTIONS.CREATE:
269
+ impacted = [{"new": data}]
270
+ elif action == ACTIONS.DELETE:
271
+ if not isinstance(data, list):
272
+ impacted = [{"new": data, "old": old}]
273
+ else:
274
+ impacted = []
275
+ for i, new in enumerate(data):
276
+ impacted.append({"new": new, "old": old[i]})
277
+ else: # ACTIONS.UPDATE:
278
+ impacted = [{"new": data, "old": old}]
279
+
280
+ # Get previously triggered events.
281
+ events = request.bound_data.setdefault("resource_events", EventCollector())
282
+
283
+ resource_name = resource_name or request.current_resource_name
284
+ matchdict = resource_data or dict(request.matchdict)
285
+
286
+ payload = {
287
+ "timestamp": timestamp,
288
+ "action": action.value,
289
+ # Deprecated: don't actually use URI (see #945).
290
+ "uri": strip_uri_prefix(request.path),
291
+ "user_id": request.prefixed_userid,
292
+ "resource_name": resource_name,
293
+ }
294
+
295
+ # Deprecated: don't actually use `resource_name_id` either (see #945).
296
+ if "id" in request.matchdict:
297
+ matchdict[resource_name + "_id"] = matchdict.pop("id")
298
+
299
+ payload.update(**matchdict)
300
+
301
+ events.add_event(resource_name, parent_id, action, payload, impacted, request)