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,230 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
import colander
|
|
5
|
+
from pyramid.settings import asbool
|
|
6
|
+
|
|
7
|
+
from kinto.core import authorization
|
|
8
|
+
from kinto.core.cornice.validators import colander_validator
|
|
9
|
+
|
|
10
|
+
from .schema import (
|
|
11
|
+
ObjectGetQuerySchema,
|
|
12
|
+
ObjectSchema,
|
|
13
|
+
PatchHeaderSchema,
|
|
14
|
+
PayloadRequestSchema,
|
|
15
|
+
PermissionsSchema,
|
|
16
|
+
PluralGetQuerySchema,
|
|
17
|
+
PluralQuerySchema,
|
|
18
|
+
RequestSchema,
|
|
19
|
+
ResourceResponses,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
CONTENT_TYPES = ["application/json"]
|
|
24
|
+
|
|
25
|
+
PATCH_CONTENT_TYPES = ["application/merge-patch+json"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StrictSchema(colander.MappingSchema):
|
|
29
|
+
@staticmethod
|
|
30
|
+
def schema_type():
|
|
31
|
+
return colander.Mapping(unknown="raise")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PartialSchema(colander.MappingSchema):
|
|
35
|
+
@staticmethod
|
|
36
|
+
def schema_type():
|
|
37
|
+
return colander.Mapping(unknown="ignore")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SimpleSchema(colander.MappingSchema):
|
|
41
|
+
@staticmethod
|
|
42
|
+
def schema_type():
|
|
43
|
+
return colander.Mapping(unknown="preserve")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ViewSet:
|
|
47
|
+
"""The default ViewSet object.
|
|
48
|
+
|
|
49
|
+
A viewset contains all the information needed to register
|
|
50
|
+
any resource in the Cornice registry.
|
|
51
|
+
|
|
52
|
+
It provides the same features as ``cornice.resource()``, except
|
|
53
|
+
that it is much more flexible and extensible.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
service_name = "{resource_name}-{endpoint_type}"
|
|
57
|
+
plural_path = "/{resource_name}s"
|
|
58
|
+
object_path = "/{resource_name}s/{{id}}"
|
|
59
|
+
|
|
60
|
+
plural_methods = ("HEAD", "GET", "POST", "DELETE")
|
|
61
|
+
object_methods = ("GET", "PUT", "PATCH", "DELETE")
|
|
62
|
+
|
|
63
|
+
readonly_methods = ("GET", "OPTIONS", "HEAD")
|
|
64
|
+
|
|
65
|
+
factory = authorization.RouteFactory
|
|
66
|
+
|
|
67
|
+
responses = ResourceResponses()
|
|
68
|
+
|
|
69
|
+
service_arguments = {"description": "Set of {resource_name}"}
|
|
70
|
+
|
|
71
|
+
default_arguments = {
|
|
72
|
+
"permission": authorization.DYNAMIC,
|
|
73
|
+
"accept": CONTENT_TYPES,
|
|
74
|
+
"schema": RequestSchema(),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
default_post_arguments = {"content_type": CONTENT_TYPES, "schema": PayloadRequestSchema()}
|
|
78
|
+
|
|
79
|
+
default_put_arguments = {"content_type": CONTENT_TYPES, "schema": PayloadRequestSchema()}
|
|
80
|
+
|
|
81
|
+
default_patch_arguments = {
|
|
82
|
+
"content_type": CONTENT_TYPES + PATCH_CONTENT_TYPES,
|
|
83
|
+
"schema": PayloadRequestSchema().bind(header=PatchHeaderSchema()),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
default_plural_arguments = {"schema": RequestSchema().bind(querystring=PluralQuerySchema())}
|
|
87
|
+
plural_head_arguments = {
|
|
88
|
+
"schema": RequestSchema().bind(querystring=PluralGetQuerySchema()),
|
|
89
|
+
"cors_headers": (
|
|
90
|
+
"Next-Page",
|
|
91
|
+
"Last-Modified",
|
|
92
|
+
"ETag",
|
|
93
|
+
"Cache-Control",
|
|
94
|
+
"Expires",
|
|
95
|
+
"Pragma",
|
|
96
|
+
"Total-Objects",
|
|
97
|
+
"Total-Records", # Deprecated.
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
plural_get_arguments = {
|
|
101
|
+
"schema": RequestSchema().bind(querystring=PluralGetQuerySchema()),
|
|
102
|
+
"cors_headers": (
|
|
103
|
+
"Next-Page",
|
|
104
|
+
"Last-Modified",
|
|
105
|
+
"ETag",
|
|
106
|
+
"Cache-Control",
|
|
107
|
+
"Expires",
|
|
108
|
+
"Pragma",
|
|
109
|
+
),
|
|
110
|
+
}
|
|
111
|
+
plural_post_arguments = {"schema": PayloadRequestSchema()}
|
|
112
|
+
default_object_arguments = {}
|
|
113
|
+
object_get_arguments = {
|
|
114
|
+
"schema": RequestSchema().bind(querystring=ObjectGetQuerySchema()),
|
|
115
|
+
"cors_headers": ("Last-Modified", "ETag", "Cache-Control", "Expires", "Pragma"),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def __init__(self, **kwargs):
|
|
119
|
+
self.update(**kwargs)
|
|
120
|
+
self.object_arguments = functools.partial(self.get_view_arguments, "object")
|
|
121
|
+
self.plural_arguments = functools.partial(self.get_view_arguments, "plural")
|
|
122
|
+
|
|
123
|
+
def update(self, **kwargs):
|
|
124
|
+
"""Update viewset attributes with provided values."""
|
|
125
|
+
self.__dict__.update(**kwargs)
|
|
126
|
+
|
|
127
|
+
def get_view_arguments(self, endpoint_type, resource_cls, method):
|
|
128
|
+
"""Return the Pyramid/Cornice view arguments for the given endpoint
|
|
129
|
+
type and method.
|
|
130
|
+
|
|
131
|
+
:param str endpoint_type: either "plural" or "object".
|
|
132
|
+
:param resource_cls: the resource class.
|
|
133
|
+
:param str method: the HTTP method.
|
|
134
|
+
"""
|
|
135
|
+
args = {**self.default_arguments}
|
|
136
|
+
default_arguments = getattr(self, f"default_{endpoint_type}_arguments")
|
|
137
|
+
args.update(**default_arguments)
|
|
138
|
+
|
|
139
|
+
by_http_verb = f"default_{method.lower()}_arguments"
|
|
140
|
+
method_args = getattr(self, by_http_verb, {})
|
|
141
|
+
args.update(**method_args)
|
|
142
|
+
|
|
143
|
+
by_method = f"{endpoint_type}_{method.lower()}_arguments"
|
|
144
|
+
endpoint_args = getattr(self, by_method, {})
|
|
145
|
+
args.update(**endpoint_args)
|
|
146
|
+
|
|
147
|
+
request_schema = args.get("schema", RequestSchema())
|
|
148
|
+
object_schema = self.get_object_schema(resource_cls, method)
|
|
149
|
+
request_schema = request_schema.bind(body=object_schema)
|
|
150
|
+
response_schemas = self.responses.get_and_bind(endpoint_type, method, object=object_schema)
|
|
151
|
+
|
|
152
|
+
args["schema"] = request_schema
|
|
153
|
+
args["response_schemas"] = response_schemas
|
|
154
|
+
|
|
155
|
+
validators = args.get("validators", [])
|
|
156
|
+
validators.append(colander_validator)
|
|
157
|
+
args["validators"] = validators
|
|
158
|
+
|
|
159
|
+
return args
|
|
160
|
+
|
|
161
|
+
def get_object_schema(self, resource_cls, method):
|
|
162
|
+
"""Return the Cornice schema for the given method."""
|
|
163
|
+
if method.lower() in ("patch", "delete"):
|
|
164
|
+
resource_schema = SimpleSchema
|
|
165
|
+
else:
|
|
166
|
+
resource_schema = resource_cls.schema
|
|
167
|
+
|
|
168
|
+
permissions = PermissionsSchema(
|
|
169
|
+
name="permissions", missing=colander.drop, permissions=resource_cls.permissions
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
object_schema = ObjectSchema().bind(data=resource_schema(), permissions=permissions)
|
|
173
|
+
|
|
174
|
+
return object_schema
|
|
175
|
+
|
|
176
|
+
def get_view(self, endpoint_type, method):
|
|
177
|
+
"""Return the view method name located on the resource object, for the
|
|
178
|
+
given type and method.
|
|
179
|
+
|
|
180
|
+
* For plural, this will be "plural_{method|lower}
|
|
181
|
+
* For objects, this will be "{method|lower}.
|
|
182
|
+
"""
|
|
183
|
+
if endpoint_type == "object":
|
|
184
|
+
return method.lower()
|
|
185
|
+
return f"{endpoint_type}_{method.lower()}"
|
|
186
|
+
|
|
187
|
+
def get_name(self, resource_cls):
|
|
188
|
+
"""Returns the name of the resource."""
|
|
189
|
+
# Provided on viewset during registration.
|
|
190
|
+
if "name" in self.__dict__:
|
|
191
|
+
return self.__dict__["name"]
|
|
192
|
+
|
|
193
|
+
# Attribute on resource class (but not @property)
|
|
194
|
+
has_class_attr = hasattr(resource_cls, "name") and not callable(resource_cls.name)
|
|
195
|
+
if has_class_attr:
|
|
196
|
+
return resource_cls.name
|
|
197
|
+
|
|
198
|
+
# Use classname
|
|
199
|
+
return resource_cls.__name__.lower()
|
|
200
|
+
|
|
201
|
+
def get_service_name(self, endpoint_type, resource_cls):
|
|
202
|
+
"""Returns the name of the service, depending a given type and
|
|
203
|
+
resource.
|
|
204
|
+
"""
|
|
205
|
+
return self.service_name.format(
|
|
206
|
+
resource_name=self.get_name(resource_cls), endpoint_type=endpoint_type
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def get_service_arguments(self):
|
|
210
|
+
return {**self.service_arguments, "factory": self.factory}
|
|
211
|
+
|
|
212
|
+
def is_endpoint_enabled(self, endpoint_type, resource_name, method, settings):
|
|
213
|
+
"""Returns if the given endpoint is enabled or not.
|
|
214
|
+
|
|
215
|
+
Uses the settings to tell so.
|
|
216
|
+
"""
|
|
217
|
+
readonly_enabled = asbool(settings.get("readonly"))
|
|
218
|
+
readonly_method = method.lower() in [m.lower() for m in self.readonly_methods]
|
|
219
|
+
if readonly_enabled and not readonly_method:
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
setting_enabled = f"{endpoint_type}_{resource_name}_{method.lower()}_enabled"
|
|
223
|
+
return asbool(settings.get(setting_enabled, True))
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class ShareableViewSet(ViewSet):
|
|
227
|
+
def __init__(self, *args, **kwargs):
|
|
228
|
+
message = "`ShareableViewSet` is deprecated, use `ViewSet` instead."
|
|
229
|
+
warnings.warn(message, DeprecationWarning)
|
|
230
|
+
super().__init__(*args, **kwargs)
|
kinto/core/schema.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""This module contains generic schemas used by the core. You should declare schemas that
|
|
2
|
+
may be reused across the `kinto.core` here.
|
|
3
|
+
|
|
4
|
+
.. note:: This module is reserve for generic schemas only.
|
|
5
|
+
|
|
6
|
+
- If a schema is resource specific, you should use `kinto.core.resource.schema`.
|
|
7
|
+
- If a schema is view specific, you should declare it on the respective view.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import colander
|
|
11
|
+
|
|
12
|
+
from kinto.core.utils import msec_time, native_value, strip_whitespace
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TimeStamp(colander.SchemaNode):
|
|
16
|
+
"""Basic integer schema field that can be set to current server timestamp
|
|
17
|
+
in milliseconds if no value is provided.
|
|
18
|
+
|
|
19
|
+
.. code-block:: python
|
|
20
|
+
|
|
21
|
+
class Book(ResourceSchema):
|
|
22
|
+
added_on = TimeStamp()
|
|
23
|
+
read_on = TimeStamp(auto_now=False, missing=-1)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
schema_type = colander.Integer
|
|
27
|
+
|
|
28
|
+
title = "Epoch timestamp"
|
|
29
|
+
"""Default field title."""
|
|
30
|
+
|
|
31
|
+
auto_now = True
|
|
32
|
+
"""Set to current server timestamp (*milliseconds*) if not provided."""
|
|
33
|
+
|
|
34
|
+
missing = None
|
|
35
|
+
"""Default field value if not provided in object."""
|
|
36
|
+
|
|
37
|
+
def deserialize(self, cstruct=colander.null):
|
|
38
|
+
if cstruct is colander.null and self.auto_now:
|
|
39
|
+
cstruct = msec_time()
|
|
40
|
+
return super(TimeStamp, self).deserialize(cstruct)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class URL(colander.SchemaNode):
|
|
44
|
+
"""String field representing a URL, with max length of 2048.
|
|
45
|
+
This is basically a shortcut for string field with
|
|
46
|
+
`~colander:colander.url`.
|
|
47
|
+
|
|
48
|
+
.. code-block:: python
|
|
49
|
+
|
|
50
|
+
class BookmarkSchema(ResourceSchema):
|
|
51
|
+
url = URL()
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
schema_type = colander.String
|
|
55
|
+
validator = colander.All(colander.url, colander.Length(min=1, max=2048))
|
|
56
|
+
|
|
57
|
+
def preparer(self, appstruct):
|
|
58
|
+
return strip_whitespace(appstruct)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Any(colander.SchemaType):
|
|
62
|
+
"""Colander type agnostic field."""
|
|
63
|
+
|
|
64
|
+
def deserialize(self, node, cstruct):
|
|
65
|
+
return cstruct
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class HeaderField(colander.SchemaNode):
|
|
69
|
+
"""Basic header field SchemaNode."""
|
|
70
|
+
|
|
71
|
+
missing = colander.drop
|
|
72
|
+
|
|
73
|
+
def deserialize(self, cstruct=colander.null):
|
|
74
|
+
if isinstance(cstruct, bytes):
|
|
75
|
+
try:
|
|
76
|
+
cstruct = cstruct.decode("utf-8")
|
|
77
|
+
except UnicodeDecodeError:
|
|
78
|
+
raise colander.Invalid(self, msg="Headers should be UTF-8 encoded")
|
|
79
|
+
return super(HeaderField, self).deserialize(cstruct)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class QueryField(colander.SchemaNode):
|
|
83
|
+
"""Basic querystring field SchemaNode."""
|
|
84
|
+
|
|
85
|
+
missing = colander.drop
|
|
86
|
+
|
|
87
|
+
def deserialize(self, cstruct=colander.null):
|
|
88
|
+
if isinstance(cstruct, str):
|
|
89
|
+
cstruct = native_value(cstruct)
|
|
90
|
+
return super(QueryField, self).deserialize(cstruct)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class FieldList(QueryField):
|
|
94
|
+
"""String field representing a list of attributes."""
|
|
95
|
+
|
|
96
|
+
schema_type = colander.Sequence
|
|
97
|
+
error_message = "The value should be a list of comma separated attributes"
|
|
98
|
+
missing = colander.drop
|
|
99
|
+
fields = colander.SchemaNode(colander.String(), missing=colander.drop)
|
|
100
|
+
|
|
101
|
+
def deserialize(self, cstruct=colander.null):
|
|
102
|
+
if isinstance(cstruct, str):
|
|
103
|
+
cstruct = cstruct.split(",")
|
|
104
|
+
return super(FieldList, self).deserialize(cstruct)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class HeaderQuotedInteger(HeaderField):
|
|
108
|
+
"""Integer between "" used in precondition headers."""
|
|
109
|
+
|
|
110
|
+
schema_type = colander.String
|
|
111
|
+
error_message = "The value should be integer between double quotes."
|
|
112
|
+
validator = colander.Regex('^"([0-9]+?)"$|\\*', msg=error_message)
|
|
113
|
+
|
|
114
|
+
def deserialize(self, cstruct=colander.null):
|
|
115
|
+
param = super(HeaderQuotedInteger, self).deserialize(cstruct)
|
|
116
|
+
if param is colander.drop or param == "*":
|
|
117
|
+
return param
|
|
118
|
+
|
|
119
|
+
return int(param[1:-1])
|
kinto/core/scripts.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
kinto.core.scripts: utilities to build admin scripts for kinto-based services
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from pyramid.settings import asbool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def migrate(env, dry_run=False):
|
|
14
|
+
"""
|
|
15
|
+
User-friendly frontend to run database migrations.
|
|
16
|
+
"""
|
|
17
|
+
registry = env["registry"]
|
|
18
|
+
settings = registry.settings
|
|
19
|
+
readonly_backends = ("storage", "permission")
|
|
20
|
+
readonly_mode = asbool(settings.get("readonly", False))
|
|
21
|
+
|
|
22
|
+
for backend in ("cache", "storage", "permission"):
|
|
23
|
+
if hasattr(registry, backend):
|
|
24
|
+
if readonly_mode and backend in readonly_backends:
|
|
25
|
+
message = f"Cannot migrate the {backend} backend while in readonly mode."
|
|
26
|
+
logger.error(message)
|
|
27
|
+
else:
|
|
28
|
+
getattr(registry, backend).initialize_schema(dry_run=dry_run)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def purge_deleted(env, resource_names, max_retained):
|
|
32
|
+
logger.info("Keep only %r tombstones per parent and resource." % max_retained)
|
|
33
|
+
|
|
34
|
+
registry = env["registry"]
|
|
35
|
+
|
|
36
|
+
count = 0
|
|
37
|
+
for resource_name in resource_names:
|
|
38
|
+
count += registry.storage.purge_deleted(
|
|
39
|
+
resource_name=resource_name, parent_id="*", max_retained=max_retained
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
logger.info("%s tombstone(s) deleted." % count)
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def flush_cache(env):
|
|
47
|
+
registry = env["registry"]
|
|
48
|
+
registry.cache.flush()
|
|
49
|
+
logger.info("Cache has been cleared.")
|
|
50
|
+
return 0
|
kinto/core/statsd.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from kinto.plugins.statsd import load_from_config # noqa: F401
|