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,502 @@
1
+ import warnings
2
+
3
+ import colander
4
+
5
+ from kinto.core.errors import ErrorSchema
6
+ from kinto.core.schema import (
7
+ URL,
8
+ Any,
9
+ FieldList,
10
+ HeaderField,
11
+ HeaderQuotedInteger,
12
+ QueryField,
13
+ TimeStamp,
14
+ )
15
+ from kinto.core.utils import native_value
16
+
17
+
18
+ POSTGRESQL_MAX_INTEGER_VALUE = 2**63
19
+
20
+ positive_big_integer = colander.Range(min=0, max=POSTGRESQL_MAX_INTEGER_VALUE)
21
+
22
+
23
+ class TimeStamp(TimeStamp):
24
+ """This schema is deprecated, you should use `kinto.core.schema.TimeStamp` instead."""
25
+
26
+ def __init__(self, *args, **kwargs):
27
+ message = (
28
+ "`kinto.core.resource.schema.TimeStamp` is deprecated, "
29
+ "use `kinto.core.schema.TimeStamp` instead."
30
+ )
31
+ warnings.warn(message, DeprecationWarning)
32
+ super().__init__(*args, **kwargs)
33
+
34
+
35
+ class URL(URL):
36
+ """This schema is deprecated, you should use `kinto.core.schema.URL` instead."""
37
+
38
+ def __init__(self, *args, **kwargs):
39
+ message = (
40
+ "`kinto.core.resource.schema.URL` is deprecated, use `kinto.core.schema.URL` instead."
41
+ )
42
+ warnings.warn(message, DeprecationWarning)
43
+ super().__init__(*args, **kwargs)
44
+
45
+
46
+ # Resource related schemas
47
+
48
+
49
+ class ResourceSchema(colander.MappingSchema):
50
+ """Base resource schema, with *Cliquet* specific built-in options."""
51
+
52
+ class Options:
53
+ """
54
+ Resource schema options.
55
+
56
+ This is meant to be overriden for changing values:
57
+
58
+ .. code-block:: python
59
+
60
+ class Product(ResourceSchema):
61
+ reference = colander.SchemaNode(colander.String())
62
+
63
+ class Options:
64
+ readonly_fields = ('reference',)
65
+ """
66
+
67
+ readonly_fields = tuple()
68
+ """Fields that cannot be updated. Values for fields will have to be
69
+ provided either during object creation, through default values using
70
+ ``missing`` attribute or implementing a custom logic in
71
+ :meth:`kinto.core.resource.Resource.process_object`.
72
+ """
73
+
74
+ preserve_unknown = True
75
+ """Define if unknown fields should be preserved or not.
76
+
77
+ The resource is schema-less by default. In other words, any field name
78
+ will be accepted on objects. Set this to ``False`` in order to limit
79
+ the accepted fields to the ones defined in the schema.
80
+ """
81
+
82
+ @classmethod
83
+ def get_option(cls, attr):
84
+ default_value = getattr(ResourceSchema.Options, attr)
85
+ return getattr(cls.Options, attr, default_value)
86
+
87
+ @classmethod
88
+ def is_readonly(cls, field):
89
+ """Return True if specified field name is read-only.
90
+
91
+ :param str field: the field name in the schema
92
+ :returns: ``True`` if the specified field is read-only,
93
+ ``False`` otherwise.
94
+ :rtype: bool
95
+ """
96
+ return field in cls.get_option("readonly_fields")
97
+
98
+ def schema_type(self):
99
+ if self.get_option("preserve_unknown") is True:
100
+ unknown = "preserve"
101
+ else:
102
+ unknown = "ignore"
103
+ return colander.Mapping(unknown=unknown)
104
+
105
+
106
+ class PermissionsSchema(colander.SchemaNode):
107
+ """A permission mapping defines ACEs.
108
+
109
+ It has permission names as keys and principals as values.
110
+
111
+ ::
112
+
113
+ {
114
+ "write": ["fxa:af3e077eb9f5444a949ad65aa86e82ff"],
115
+ "groups:create": ["fxa:70a9335eecfe440fa445ba752a750f3d"]
116
+ }
117
+
118
+ """
119
+
120
+ def __init__(self, *args, **kwargs):
121
+ self.known_perms = kwargs.pop("permissions", tuple())
122
+ super().__init__(*args, **kwargs)
123
+
124
+ for perm in self.known_perms:
125
+ self[perm] = self._get_node_principals(perm)
126
+
127
+ def schema_type(self):
128
+ if self.known_perms:
129
+ return colander.Mapping(unknown="raise")
130
+ else:
131
+ return colander.Mapping(unknown="preserve")
132
+
133
+ def deserialize(self, cstruct=colander.null):
134
+ # If permissions are not a mapping (e.g null or invalid), try deserializing
135
+ if not isinstance(cstruct, dict):
136
+ return super().deserialize(cstruct)
137
+
138
+ # If using application/merge-patch+json we need to allow null values as they
139
+ # represent removing a key.
140
+ cstruct, removed_keys = self._preprocess_null_perms(cstruct)
141
+
142
+ # If permissions are listed, check fields and produce fancy error messages
143
+ if self.known_perms:
144
+ for perm in cstruct:
145
+ colander.OneOf(choices=self.known_perms)(self, perm)
146
+ permissions = super().deserialize(cstruct)
147
+
148
+ # Else deserialize the fields that are not on the schema
149
+ else:
150
+ permissions = {}
151
+ perm_schema = colander.SequenceSchema(colander.SchemaNode(colander.String()))
152
+ for perm, principals in cstruct.items():
153
+ permissions[perm] = perm_schema.deserialize(principals)
154
+
155
+ return self._postprocess_null_perms(permissions, removed_keys)
156
+
157
+ def _get_node_principals(self, perm):
158
+ principal = colander.SchemaNode(colander.String())
159
+ return colander.SchemaNode(
160
+ colander.Sequence(), principal, name=perm, missing=colander.drop
161
+ )
162
+
163
+ @staticmethod
164
+ def _preprocess_null_perms(cstruct):
165
+ keys = {k for k, v in cstruct.items() if v is None}
166
+ cleaned = {k: v for k, v in cstruct.items() if v is not None}
167
+ return cleaned, keys
168
+
169
+ @staticmethod
170
+ def _postprocess_null_perms(validated, keys):
171
+ validated.update({k: None for k in keys})
172
+ return validated
173
+
174
+
175
+ # Header schemas
176
+
177
+
178
+ class HeaderSchema(colander.MappingSchema):
179
+ """Base schema used for validating and deserializing request headers."""
180
+
181
+ missing = colander.drop
182
+
183
+ if_match = HeaderQuotedInteger(name="If-Match")
184
+ if_none_match = HeaderQuotedInteger(name="If-None-Match")
185
+
186
+ @staticmethod
187
+ def schema_type():
188
+ return colander.Mapping(unknown="preserve")
189
+
190
+
191
+ class PatchHeaderSchema(HeaderSchema):
192
+ """Header schema used with PATCH requests."""
193
+
194
+ def response_behavior_validator():
195
+ return colander.OneOf(["full", "light", "diff"])
196
+
197
+ response_behaviour = HeaderField(
198
+ colander.String(), name="Response-Behavior", validator=response_behavior_validator()
199
+ )
200
+
201
+
202
+ # Querystring schemas
203
+
204
+
205
+ class QuerySchema(colander.MappingSchema):
206
+ """
207
+ Schema used for validating and deserializing querystrings. It will include
208
+ and try to guess the type of unknown fields (field filters) on deserialization.
209
+ """
210
+
211
+ missing = colander.drop
212
+
213
+ @staticmethod
214
+ def schema_type():
215
+ return colander.Mapping(unknown="ignore")
216
+
217
+ def deserialize(self, cstruct=colander.null):
218
+ """
219
+ Deserialize and validate the QuerySchema fields and try to deserialize and
220
+ get the native value of additional filds (field filters) that may be present
221
+ on the cstruct.
222
+
223
+ e.g:: ?exclude_id=a,b&deleted=true -> {'exclude_id': ['a', 'b'], deleted: True}
224
+ """
225
+ values = {}
226
+
227
+ schema_values = super().deserialize(cstruct)
228
+
229
+ # Deserialize querystring field filters (see docstring e.g)
230
+ for k, v in cstruct.items():
231
+ # Deserialize lists used on contains_ and contains_any_ filters
232
+ if k.startswith("contains_"):
233
+ as_list = native_value(v)
234
+
235
+ if not isinstance(as_list, list):
236
+ values[k] = [as_list]
237
+ else:
238
+ values[k] = as_list
239
+
240
+ # Deserialize lists used on in_ and exclude_ filters
241
+ elif k.startswith("in_") or k.startswith("exclude_"):
242
+ as_list = FieldList().deserialize(v)
243
+ values[k] = [native_value(v) for v in as_list]
244
+ else:
245
+ values[k] = native_value(v)
246
+
247
+ values.update(schema_values)
248
+ return values
249
+
250
+
251
+ class PluralQuerySchema(QuerySchema):
252
+ """Querystring schema used with plural endpoints."""
253
+
254
+ _limit = QueryField(colander.Integer(), validator=positive_big_integer)
255
+ _sort = FieldList()
256
+ _token = QueryField(colander.String())
257
+ _since = QueryField(colander.Integer(), validator=positive_big_integer)
258
+ _to = QueryField(colander.Integer(), validator=positive_big_integer)
259
+ _before = QueryField(colander.Integer(), validator=positive_big_integer)
260
+ id = QueryField(colander.String())
261
+ last_modified = QueryField(colander.Integer(), validator=positive_big_integer)
262
+
263
+
264
+ class ObjectGetQuerySchema(QuerySchema):
265
+ """Querystring schema for GET object requests."""
266
+
267
+ _fields = FieldList()
268
+
269
+
270
+ class PluralGetQuerySchema(PluralQuerySchema):
271
+ """Querystring schema for GET plural endpoints requests."""
272
+
273
+ _fields = FieldList()
274
+
275
+
276
+ # Body Schemas
277
+
278
+
279
+ class ObjectSchema(colander.MappingSchema):
280
+ @colander.deferred
281
+ def data(node, kwargs):
282
+ data = kwargs.get("data")
283
+ if data:
284
+ # Check if empty object is allowed.
285
+ # (e.g every schema fields have defaults)
286
+ try:
287
+ data.deserialize({})
288
+ except colander.Invalid:
289
+ pass
290
+ else:
291
+ data.default = {}
292
+ data.missing = colander.drop
293
+ return data
294
+
295
+ @colander.deferred
296
+ def permissions(node, kwargs):
297
+ def get_perms(node, kwargs):
298
+ return kwargs.get("permissions")
299
+
300
+ # Set if node is provided, else keep deferred. This allows binding the body
301
+ # on Resource first and bind permissions later.
302
+ # XXX: probably not necessary now that UserResource is gone.
303
+ return get_perms(node, kwargs) or colander.deferred(get_perms)
304
+
305
+ @staticmethod
306
+ def schema_type():
307
+ return colander.Mapping(unknown="raise")
308
+
309
+
310
+ class JsonPatchOperationSchema(colander.MappingSchema):
311
+ """Single JSON Patch Operation."""
312
+
313
+ def op_validator():
314
+ op_values = ["test", "add", "remove", "replace", "move", "copy"]
315
+ return colander.OneOf(op_values)
316
+
317
+ def path_validator():
318
+ return colander.Regex("(/\\w*)+")
319
+
320
+ op = colander.SchemaNode(colander.String(), validator=op_validator())
321
+ path = colander.SchemaNode(colander.String(), validator=path_validator())
322
+ from_ = colander.SchemaNode(
323
+ colander.String(), name="from", validator=path_validator(), missing=colander.drop
324
+ )
325
+ value = colander.SchemaNode(Any(), missing=colander.drop)
326
+
327
+ @staticmethod
328
+ def schema_type():
329
+ return colander.Mapping(unknown="raise")
330
+
331
+
332
+ class JsonPatchBodySchema(colander.SequenceSchema):
333
+ """Body used with JSON Patch (application/json-patch+json) as in RFC 6902."""
334
+
335
+ operations = JsonPatchOperationSchema(missing=colander.drop)
336
+
337
+
338
+ # Request schemas
339
+
340
+
341
+ class RequestSchema(colander.MappingSchema):
342
+ """Base schema for kinto requests."""
343
+
344
+ @colander.deferred
345
+ def header(node, kwargs):
346
+ return kwargs.get("header")
347
+
348
+ @colander.deferred
349
+ def querystring(node, kwargs):
350
+ return kwargs.get("querystring")
351
+
352
+ def after_bind(self, node, kw):
353
+ # Set default bindings
354
+ if not self.get("header"):
355
+ self["header"] = HeaderSchema()
356
+ if not self.get("querystring"):
357
+ self["querystring"] = QuerySchema()
358
+
359
+
360
+ class PayloadRequestSchema(RequestSchema):
361
+ """Base schema for methods that use a JSON request body."""
362
+
363
+ @colander.deferred
364
+ def body(node, kwargs):
365
+ def get_body(node, kwargs):
366
+ return kwargs.get("body")
367
+
368
+ # Set if node is provided, else keep deferred (and allow bindind later)
369
+ return get_body(node, kwargs) or colander.deferred(get_body)
370
+
371
+
372
+ class JsonPatchRequestSchema(RequestSchema):
373
+ """JSON Patch (application/json-patch+json) request schema."""
374
+
375
+ body = JsonPatchBodySchema()
376
+ querystring = QuerySchema()
377
+ header = PatchHeaderSchema()
378
+
379
+
380
+ # Response schemas
381
+
382
+
383
+ class ResponseHeaderSchema(colander.MappingSchema):
384
+ """Kinto API custom response headers."""
385
+
386
+ etag = HeaderQuotedInteger(name="Etag")
387
+ last_modified = colander.SchemaNode(colander.String(), name="Last-Modified")
388
+
389
+
390
+ class ErrorResponseSchema(colander.MappingSchema):
391
+ """Response schema used on 4xx and 5xx errors."""
392
+
393
+ body = ErrorSchema()
394
+
395
+
396
+ class NotModifiedResponseSchema(colander.MappingSchema):
397
+ """Response schema used on 304 Not Modified responses."""
398
+
399
+ header = ResponseHeaderSchema()
400
+
401
+
402
+ class ObjectResponseSchema(colander.MappingSchema):
403
+ """Response schema used with sigle resource endpoints."""
404
+
405
+ header = ResponseHeaderSchema()
406
+
407
+ @colander.deferred
408
+ def body(node, kwargs):
409
+ return kwargs.get("object")
410
+
411
+
412
+ class PluralResponseSchema(colander.MappingSchema):
413
+ """Response schema used with plural endpoints."""
414
+
415
+ header = ResponseHeaderSchema()
416
+
417
+ @colander.deferred
418
+ def body(node, kwargs):
419
+ resource = kwargs.get("object")["data"]
420
+ datalist = colander.MappingSchema()
421
+ datalist["data"] = colander.SequenceSchema(resource, missing=[])
422
+ return datalist
423
+
424
+
425
+ class ResourceResponses:
426
+ """Class that wraps and handles Resource responses."""
427
+
428
+ default_schemas = {
429
+ "400": ErrorResponseSchema(description="The request is invalid."),
430
+ "401": ErrorResponseSchema(description="The request is missing authentication headers."),
431
+ "403": ErrorResponseSchema(
432
+ description=(
433
+ "The user is not allowed to perform the operation, "
434
+ "or the resource is not accessible."
435
+ )
436
+ ),
437
+ "406": ErrorResponseSchema(
438
+ description="The client doesn't accept supported responses Content-Type."
439
+ ),
440
+ "412": ErrorResponseSchema(
441
+ description="Object was changed or deleted since value in `If-Match` header."
442
+ ),
443
+ "default": ErrorResponseSchema(description="Unexpected error."),
444
+ }
445
+ default_object_schemas = {"200": ObjectResponseSchema(description="Return the target object.")}
446
+ default_plural_schemas = {
447
+ "200": PluralResponseSchema(description="Return a list of matching objects.")
448
+ }
449
+ default_get_schemas = {
450
+ "304": NotModifiedResponseSchema(
451
+ description="Response has not changed since value in If-None-Match header"
452
+ )
453
+ }
454
+ default_post_schemas = {
455
+ "200": ObjectResponseSchema(description="Return an existing object."),
456
+ "201": ObjectResponseSchema(description="Return a created object."),
457
+ "415": ErrorResponseSchema(
458
+ description="The client request was not sent with a correct Content-Type."
459
+ ),
460
+ }
461
+ default_put_schemas = {
462
+ "201": ObjectResponseSchema(description="Return created object."),
463
+ "415": ErrorResponseSchema(
464
+ description="The client request was not sent with a correct Content-Type."
465
+ ),
466
+ }
467
+ default_patch_schemas = {
468
+ "415": ErrorResponseSchema(
469
+ description="The client request was not sent with a correct Content-Type."
470
+ )
471
+ }
472
+ default_delete_schemas = {}
473
+ object_get_schemas = {
474
+ "404": ErrorResponseSchema(description="The object does not exist or was deleted.")
475
+ }
476
+ object_patch_schemas = {
477
+ "404": ErrorResponseSchema(description="The object does not exist or was deleted.")
478
+ }
479
+ object_delete_schemas = {
480
+ "404": ErrorResponseSchema(description="The object does not exist or was already deleted.")
481
+ }
482
+
483
+ def get_and_bind(self, endpoint_type, method, **kwargs):
484
+ """Wrap resource colander response schemas for an endpoint and return a dict
485
+ of status codes mapping cloned and binded responses."""
486
+
487
+ responses = self.default_schemas.copy()
488
+ type_responses = getattr(self, f"default_{endpoint_type}_schemas")
489
+ responses.update(**type_responses)
490
+
491
+ verb_responses = f"default_{method.lower()}_schemas"
492
+ method_args = getattr(self, verb_responses, {})
493
+ responses.update(**method_args)
494
+
495
+ method_responses = f"{endpoint_type}_{method.lower()}_schemas"
496
+ endpoint_args = getattr(self, method_responses, {})
497
+ responses.update(**endpoint_args)
498
+
499
+ # Bind and clone schemas into a new dict
500
+ bound = {code: resp.bind(**kwargs) for code, resp in responses.items()}
501
+
502
+ return bound