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,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