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,235 @@
1
+ import re
2
+
3
+ import colander
4
+ from pyramid.settings import aslist
5
+
6
+ from kinto.authorization import PERMISSIONS_INHERITANCE_TREE
7
+ from kinto.core import resource
8
+ from kinto.core import utils as core_utils
9
+ from kinto.core.storage import Sort
10
+ from kinto.core.storage.memory import extract_object_set
11
+
12
+
13
+ def allowed_from_settings(settings, principals):
14
+ """Returns every permissions allowed from settings for the current user.
15
+ :param settings dict: app settings
16
+ :param principals list: list of principals of current user
17
+ :rtype: dict
18
+
19
+ Result example::
20
+
21
+ {
22
+ "bucket": {"write", "collection:create"},
23
+ "collection": {"read"}
24
+ }
25
+
26
+ XXX: This helper will be useful for Kinto/kinto#894
27
+ """
28
+ # Select settings about explicit permissions set on resources
29
+ # bucket_write_principals = ... --> {("bucket", "write"): ["account:admin"]}
30
+ perms_settings = {
31
+ tuple(k.split("_", 3)[:2]): aslist(v)
32
+ for k, v in settings.items()
33
+ if re.match("(.+)_(create|write|read)_principals", k)
34
+ }
35
+
36
+ from_settings = {}
37
+ for (resource_name, permission), allowed_principals in perms_settings.items():
38
+ # Keep the known permissions only.
39
+ if resource_name not in PERMISSIONS_INHERITANCE_TREE.keys():
40
+ continue
41
+ # Keep the permissions of the current user only.
42
+ if not bool(set(principals) & set(allowed_principals)):
43
+ continue
44
+ # ``collection_create_principals`` means ``collection:create`` in bucket.
45
+ if permission == "create":
46
+ permission = f"{resource_name}:{permission}"
47
+ resource_name = { # resource parents.
48
+ "collection": "bucket",
49
+ "group": "bucket",
50
+ "record": "collection",
51
+ "bucket": "root",
52
+ "account": "root",
53
+ }.get(resource_name, "")
54
+ # Store them in a convenient way.
55
+ from_settings.setdefault(resource_name, set()).add(permission)
56
+ return from_settings
57
+
58
+
59
+ class PermissionsModel:
60
+ id_field = "id"
61
+ modified_field = "last_modified"
62
+ deleted_field = "deleted"
63
+
64
+ def __init__(self, request):
65
+ self.request = request
66
+
67
+ def timestamp(self, parent_id=None):
68
+ return 0
69
+
70
+ def get_objects(
71
+ self,
72
+ filters=None,
73
+ sorting=None,
74
+ pagination_rules=None,
75
+ limit=None,
76
+ include_deleted=False,
77
+ parent_id=None,
78
+ ):
79
+ objects, _ = self._get_objects(
80
+ filters=filters,
81
+ sorting=sorting,
82
+ pagination_rules=pagination_rules,
83
+ limit=limit,
84
+ include_deleted=include_deleted,
85
+ parent_id=parent_id,
86
+ )
87
+ return objects
88
+
89
+ def count_objects(self, filters=None, parent_id=None):
90
+ _, count = self._get_objects(filters=filters, parent_id=parent_id)
91
+ return count
92
+
93
+ def _get_objects(
94
+ self,
95
+ filters=None,
96
+ sorting=None,
97
+ pagination_rules=None,
98
+ limit=None,
99
+ include_deleted=False,
100
+ parent_id=None,
101
+ ):
102
+ # Invert the permissions inheritance tree.
103
+ perms_descending_tree = {}
104
+ for on_resource, tree in PERMISSIONS_INHERITANCE_TREE.items():
105
+ for obtained_perm, obtained_from in tree.items():
106
+ for from_resource, perms in obtained_from.items():
107
+ for perm in perms:
108
+ perms_descending_tree.setdefault(from_resource, {}).setdefault(
109
+ perm, {}
110
+ ).setdefault(on_resource, set()).add(obtained_perm)
111
+
112
+ # Obtain current principals.
113
+ principals = self.request.prefixed_principals
114
+
115
+ # Query every possible permission of the current user from backend.
116
+ backend = self.request.registry.permission
117
+ perms_by_object_uri = backend.get_accessible_objects(principals)
118
+
119
+ # Check settings for every allowed resources.
120
+ from_settings = allowed_from_settings(self.request.registry.settings, principals)
121
+
122
+ # Add additional resources and permissions defined in settings/plugins
123
+ for root_perm in from_settings.get("root", []):
124
+ resource_name, _ = root_perm.split(":")
125
+ perms_by_object_uri.setdefault("/", set()).add(root_perm)
126
+ perms_descending_tree.setdefault("root", {}).update({root_perm: {"root": {root_perm}}})
127
+
128
+ # Expand permissions obtained from backend with the object URIs that
129
+ # correspond to permissions allowed from settings.
130
+ allowed_resources = {"bucket", "collection", "group"} & set(from_settings.keys())
131
+ if allowed_resources:
132
+ storage = self.request.registry.storage
133
+ every_bucket = storage.list_all(parent_id="", resource_name="bucket")
134
+ for bucket in every_bucket:
135
+ bucket_uri = "/buckets/{id}".format_map(bucket)
136
+ for res in allowed_resources:
137
+ resource_perms = from_settings[res]
138
+ # Bucket is always fetched.
139
+ if res == "bucket":
140
+ perms_by_object_uri.setdefault(bucket_uri, set()).update(resource_perms)
141
+ continue
142
+ # Fetch bucket collections and groups.
143
+ # XXX: wrong approach: query in a loop!
144
+ every_subobjects = storage.list_all(parent_id=bucket_uri, resource_name=res)
145
+ for subobject in every_subobjects:
146
+ subobj_uri = bucket_uri + f"/{res}s/{subobject['id']}"
147
+ perms_by_object_uri.setdefault(subobj_uri, set()).update(resource_perms)
148
+
149
+ entries = []
150
+ for object_uri, perms in perms_by_object_uri.items():
151
+ try:
152
+ # Obtain associated res from object URI
153
+ resource_name, matchdict = core_utils.view_lookup(self.request, object_uri)
154
+ except ValueError:
155
+ # Skip permissions entries that are not linked to an object URI
156
+ continue
157
+
158
+ # For consistency with event payloads, if resource has an id,
159
+ # prefix it with its resource name
160
+ if "id" in matchdict:
161
+ matchdict[resource_name + "_id"] = matchdict["id"]
162
+
163
+ # The imaginary "root" resource gets mapped to the hello
164
+ # view. Handle it explicitly.
165
+ if resource_name == "hello":
166
+ resource_name = "root"
167
+
168
+ # Expand implicit permissions using descending tree.
169
+ permissions = set(perms)
170
+ for perm in perms:
171
+ obtained = perms_descending_tree[resource_name][perm]
172
+ # Related to same resource only and not every sub-objects.
173
+ # (e.g "bucket:write" gives "bucket:read" but not "group:read")
174
+ permissions |= obtained[resource_name]
175
+
176
+ entry = dict(
177
+ uri=object_uri,
178
+ resource_name=resource_name,
179
+ permissions=list(permissions),
180
+ **matchdict,
181
+ )
182
+ entries.append(entry)
183
+
184
+ return extract_object_set(
185
+ entries,
186
+ filters=filters,
187
+ sorting=sorting,
188
+ id_field="uri",
189
+ pagination_rules=pagination_rules,
190
+ limit=limit,
191
+ )
192
+
193
+
194
+ class PermissionsSchema(resource.ResourceSchema):
195
+ uri = colander.SchemaNode(colander.String())
196
+ resource_name = colander.SchemaNode(colander.String())
197
+ permissions = colander.Sequence(colander.SchemaNode(colander.String()))
198
+ bucket_id = colander.SchemaNode(colander.String())
199
+ collection_id = colander.SchemaNode(colander.String(), missing=colander.drop)
200
+ group_id = colander.SchemaNode(colander.String(), missing=colander.drop)
201
+ record_id = colander.SchemaNode(colander.String(), missing=colander.drop)
202
+
203
+ class Options:
204
+ preserve_unknown = False
205
+
206
+
207
+ @resource.register(
208
+ name="permissions",
209
+ description="List of user permissions",
210
+ plural_path="/permissions",
211
+ object_path=None,
212
+ plural_methods=("HEAD", "GET"),
213
+ )
214
+ class Permissions(resource.Resource):
215
+ schema = PermissionsSchema
216
+
217
+ def __init__(self, request, context=None):
218
+ super().__init__(request, context)
219
+ self.model = PermissionsModel(request)
220
+
221
+ def _extract_sorting(self, limit):
222
+ # Permissions entries are not stored with timestamp, so do not
223
+ # force it.
224
+ result = super()._extract_sorting(limit)
225
+ without_last_modified = [s for s in result if s.field != self.model.modified_field]
226
+ # For pagination, there must be at least one sort criteria.
227
+ # We use ``uri`` because its values are unique.
228
+ if "uri" not in [s.field for s in without_last_modified]:
229
+ without_last_modified.append(Sort("uri", -1))
230
+ return without_last_modified
231
+
232
+ def _extract_filters(self):
233
+ result = super()._extract_filters()
234
+ without_last_modified = [s for s in result if s.field != self.model.modified_field]
235
+ return without_last_modified
kinto/views/records.py ADDED
@@ -0,0 +1,133 @@
1
+ from pyramid.authorization import Authenticated
2
+ from pyramid.settings import asbool
3
+
4
+ from kinto.core import resource, utils
5
+ from kinto.core.errors import raise_invalid
6
+ from kinto.schema_validation import (
7
+ RefResolutionError,
8
+ ValidationError,
9
+ validate_from_bucket_schema_or_400,
10
+ validate_schema,
11
+ )
12
+ from kinto.views import object_exists_or_404
13
+
14
+
15
+ _parent_path = "/buckets/{{bucket_id}}/collections/{{collection_id}}"
16
+
17
+
18
+ @resource.register(
19
+ name="record",
20
+ plural_path=_parent_path + "/records",
21
+ object_path=_parent_path + "/records/{{id}}",
22
+ )
23
+ class Record(resource.Resource):
24
+ schema_field = "schema"
25
+
26
+ def __init__(self, request, **kwargs):
27
+ # Before all, first check that the parent collection exists.
28
+ # Check if already fetched before (in batch).
29
+ collections = request.bound_data.setdefault("collections", {})
30
+ collection_uri = self.get_parent_id(request)
31
+ if collection_uri not in collections:
32
+ # Unknown yet, fetch from storage.
33
+ bucket_uri = utils.instance_uri(request, "bucket", id=self.bucket_id)
34
+ collection = object_exists_or_404(
35
+ request,
36
+ resource_name="collection",
37
+ parent_id=bucket_uri,
38
+ object_id=self.collection_id,
39
+ )
40
+ collections[collection_uri] = collection
41
+ self._collection = collections[collection_uri]
42
+
43
+ super().__init__(request, **kwargs)
44
+
45
+ def get_parent_id(self, request):
46
+ self.bucket_id = request.matchdict["bucket_id"]
47
+ self.collection_id = request.matchdict["collection_id"]
48
+ return utils.instance_uri(
49
+ request, "collection", bucket_id=self.bucket_id, id=self.collection_id
50
+ )
51
+
52
+ def process_object(self, new, old=None):
53
+ """Validate records against collection or bucket schema, if any."""
54
+ new = super().process_object(new, old)
55
+
56
+ # Is schema validation enabled?
57
+ settings = self.request.registry.settings
58
+ schema_validation = "experimental_collection_schema_validation"
59
+ if not asbool(settings.get(schema_validation)):
60
+ return new
61
+
62
+ # Remove internal and auto-assigned fields from schemas and record.
63
+ ignored_fields = (
64
+ self.model.modified_field,
65
+ self.schema_field,
66
+ self.model.permissions_field,
67
+ )
68
+
69
+ # The schema defined on the collection will be validated first.
70
+ if "schema" in self._collection:
71
+ schema = self._collection["schema"]
72
+ try:
73
+ validate_schema(
74
+ new, schema, ignore_fields=ignored_fields, id_field=self.model.id_field
75
+ )
76
+ except ValidationError as e:
77
+ raise_invalid(self.request, name=e.field, description=e.message)
78
+ except RefResolutionError as e:
79
+ raise_invalid(self.request, name="schema", description=str(e))
80
+
81
+ # Assign the schema version to the record.
82
+ schema_timestamp = self._collection[self.model.modified_field]
83
+ new[self.schema_field] = schema_timestamp
84
+
85
+ # Validate also from the record:schema field defined on the bucket.
86
+ validate_from_bucket_schema_or_400(
87
+ new,
88
+ resource_name="record",
89
+ request=self.request,
90
+ ignore_fields=ignored_fields,
91
+ id_field=self.model.id_field,
92
+ )
93
+
94
+ return new
95
+
96
+ def plural_get(self):
97
+ result = super().plural_get()
98
+ self._handle_cache_expires(self.request.response)
99
+ return result
100
+
101
+ def get(self):
102
+ result = super().get()
103
+ self._handle_cache_expires(self.request.response)
104
+ return result
105
+
106
+ def _handle_cache_expires(self, response):
107
+ """If the parent collection defines a ``cache_expires`` attribute,
108
+ then cache-control response headers are sent.
109
+
110
+ .. note::
111
+
112
+ Those headers are also sent if the
113
+ ``kinto.record_cache_expires_seconds`` setting is defined.
114
+ """
115
+ is_anonymous = Authenticated not in self.request.effective_principals
116
+ if not is_anonymous:
117
+ return
118
+
119
+ cache_expires = self._collection.get("cache_expires")
120
+ if cache_expires is None:
121
+ by_collection = f"{self.bucket_id}.{self.collection_id}.record_cache_expires_seconds"
122
+ by_bucket = f"{self.bucket_id}.record_cache_expires_seconds"
123
+ by_collection_legacy = (
124
+ f"{self.bucket_id}_{self.collection_id}_record_cache_expires_seconds"
125
+ )
126
+ by_bucket_legacy = f"{self.bucket_id}_record_cache_expires_seconds"
127
+ settings = self.request.registry.settings
128
+ for s in (by_collection, by_bucket, by_collection_legacy, by_bucket_legacy):
129
+ cache_expires = settings.get(s)
130
+ if cache_expires is not None:
131
+ break
132
+ if cache_expires is not None:
133
+ response.cache_expires(seconds=int(cache_expires))
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: kinto
3
+ Version: 23.2.1
4
+ Summary: Kinto Web Service - Store, Sync, Share, and Self-Host.
5
+ Author-email: Mozilla Services <developers@kinto-storage.org>
6
+ License: Copyright 2012 - Mozilla Foundation
7
+
8
+ Licensed under the Apache License, Version 2.0 (the "License");
9
+ you may not use this file except in compliance with the License.
10
+ You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ Unless required by applicable law or agreed to in writing, software
15
+ distributed under the License is distributed on an "AS IS" BASIS,
16
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ See the License for the specific language governing permissions and
18
+ limitations under the License.
19
+
20
+ Project-URL: Repository, https://github.com/Kinto/kinto
21
+ Keywords: web,sync,json,storage,services
22
+ Classifier: Programming Language :: Python
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3.9
25
+ Classifier: Programming Language :: Python :: 3.10
26
+ Classifier: Programming Language :: Python :: 3.11
27
+ Classifier: Programming Language :: Python :: 3.12
28
+ Classifier: Programming Language :: Python :: Implementation :: CPython
29
+ Classifier: Topic :: Internet :: WWW/HTTP
30
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
31
+ Classifier: License :: OSI Approved :: Apache Software License
32
+ Description-Content-Type: text/x-rst
33
+ License-File: LICENSE
34
+ Requires-Dist: bcrypt
35
+ Requires-Dist: colander
36
+ Requires-Dist: dockerflow
37
+ Requires-Dist: jsonschema
38
+ Requires-Dist: jsonpatch
39
+ Requires-Dist: logging-color-formatter
40
+ Requires-Dist: python-dateutil
41
+ Requires-Dist: pyramid
42
+ Requires-Dist: pyramid_multiauth
43
+ Requires-Dist: transaction
44
+ Requires-Dist: pyramid_tm
45
+ Requires-Dist: requests
46
+ Requires-Dist: waitress
47
+ Requires-Dist: python-rapidjson
48
+ Provides-Extra: redis
49
+ Requires-Dist: kinto_redis; extra == "redis"
50
+ Provides-Extra: memcached
51
+ Requires-Dist: python-memcached; extra == "memcached"
52
+ Provides-Extra: postgresql
53
+ Requires-Dist: SQLAlchemy<3; extra == "postgresql"
54
+ Requires-Dist: psycopg2-binary; extra == "postgresql"
55
+ Requires-Dist: zope.sqlalchemy; extra == "postgresql"
56
+ Provides-Extra: monitoring
57
+ Requires-Dist: newrelic; extra == "monitoring"
58
+ Requires-Dist: sentry-sdk[sqlalchemy]; extra == "monitoring"
59
+ Requires-Dist: statsd; extra == "monitoring"
60
+ Requires-Dist: werkzeug; extra == "monitoring"
61
+ Requires-Dist: prometheus-client; extra == "monitoring"
62
+ Provides-Extra: test
63
+ Requires-Dist: bravado; extra == "test"
64
+ Requires-Dist: pytest; extra == "test"
65
+ Requires-Dist: pytest-cache; extra == "test"
66
+ Requires-Dist: pytest-cov; extra == "test"
67
+ Requires-Dist: playwright; extra == "test"
68
+ Requires-Dist: webtest; extra == "test"
69
+ Provides-Extra: dev
70
+ Requires-Dist: build; extra == "dev"
71
+ Requires-Dist: ruff; extra == "dev"
72
+ Requires-Dist: twine; extra == "dev"
73
+ Requires-Dist: uwsgi; extra == "dev"
74
+ Dynamic: license-file
75
+
76
+ Kinto
77
+ =====
78
+
79
+ |coc| |gitter| |readthedocs| |pypi| |ci| |main-coverage|
80
+
81
+ .. |coc| image:: https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-blue.svg
82
+ :target: https://github.com/Kinto/kinto/blob/main/.github/CODE_OF_CONDUCT.md
83
+ :alt: Code of conduct
84
+
85
+ .. |gitter| image:: https://badges.gitter.im/Kinto/kinto.svg
86
+ :target: https://gitter.im/Kinto/kinto
87
+
88
+ .. |ci| image:: https://github.com/Kinto/kinto/actions/workflows/test.yml/badge.svg
89
+ :target: https://github.com/Kinto/kinto/actions
90
+
91
+ .. |readthedocs| image:: https://readthedocs.org/projects/kinto/badge/?version=latest
92
+ :target: https://kinto.readthedocs.io/en/latest/
93
+ :alt: Documentation Status
94
+
95
+ .. |main-coverage| image::
96
+ https://coveralls.io/repos/Kinto/kinto/badge.svg?branch=main
97
+ :alt: Coverage
98
+ :target: https://coveralls.io/r/Kinto/kinto
99
+
100
+ .. |pypi| image:: https://img.shields.io/pypi/v/kinto.svg
101
+ :target: https://pypi.python.org/pypi/kinto
102
+
103
+
104
+ Kinto is a minimalist JSON storage service with synchronisation and sharing abilities.
105
+
106
+ * `Online documentation <https://kinto.readthedocs.io/en/latest/>`_
107
+ * `Tutorial <https://kinto.readthedocs.io/en/latest/tutorials/first-steps.html>`_
108
+ * `Issue tracker <https://github.com/Kinto/kinto/issues>`_
109
+ * `Contributing <https://kinto.readthedocs.io/en/latest/community.html#how-to-contribute>`_
110
+ * `Docker Hub <https://hub.docker.com/r/kinto/kinto-server>`_
111
+
112
+ Requirements
113
+ ------------
114
+
115
+ * **Python**: 3.9+
116
+ * **Backends**: In-memory (development), PostgreSQL 9.5+ (production)
117
+
118
+ Contributors
119
+ ============
120
+
121
+ * Aaron Egaas <me@aaronegaas.com>
122
+ * Adam Chainz <adam@adamj.eu>
123
+ * Aditya Bhasin <conlini@gmail.com>
124
+ * Aiman Parvaiz <aimanparvaiz@gmail.com>
125
+ * Ajey B. Kulkarni <bkajey@gmail.com>
126
+ * Anh <anh.trinhtrung@gmail.com>
127
+ * Alexander Ryabkov <alexryabkov@gmail.com>
128
+ * Alexis Metaireau <alexis@mozilla.com>
129
+ * Alex Cottner <acottner@mozilla.com>
130
+ * Andy McKay <amckay@mozilla.com>
131
+ * Anthony Garuccio <garuccio124@gmail.com>
132
+ * Aymeric Faivre <miho@miho-stories.com>
133
+ * Ayush Sharma <ayush.aceit@gmail.com>
134
+ * Balthazar Rouberol <br@imap.cc>
135
+ * Boris Feld <lothiraldan@gmail.com>
136
+ * Brady Dufresne <dufresnebrady@gmail.com>
137
+ * Can Berk Güder <cbguder@mozilla.com>
138
+ * Castro
139
+ * Chirag B. Jadwani <chirag.jadwani@gmail.com>
140
+ * Christophe Gragnic <cgragnic@protonmail.com>
141
+ * Clément Villain <choclatefr@gmail.com>
142
+ * Dan Phrawzty <phrawzty+github@gmail.com>
143
+ * David Larlet <david@larlet.fr>
144
+ * Emamurho Ugherughe <emamurho@gmail.com>
145
+ * Enguerran Colson <enguerran@ticabri.com>
146
+ * Eric Bréhault <ebrehault@gmail.com>
147
+ * Eric Le Lay <elelay@macports.org>
148
+ * Éric Lemoine <eric.lemoine@gmail.com>
149
+ * Ethan Glasser-Camp <ethan@betacantrips.com>
150
+ * Étienne <@Étienne>
151
+ * Eugene Kulak <kulak.eugene@gmail.com>
152
+ * Fil <fil@rezo.net>
153
+ * FooBarQuaxx
154
+ * Francisco J. Piedrahita <@fpiedrah>
155
+ * Frank Bertsch <frank@mozilla.com>
156
+ * Greeshma <greeshmabalabadra@gmail.com>
157
+ * Gabriela Surita <gabsurita@gmail.com>
158
+ * George Smith <h3rmit@protonmail.com>
159
+ * Graham Beckley <gbeckley@mozilla.com>
160
+ * Greg Guthe <gguthe@mozilla.com>
161
+ * Haseeb Majid <hmajid2301@gmail.com>
162
+ * Heron Rossi <heron.rossi@hotmail.com>
163
+ * Hiromipaw <silvia@nopressure.co.uk>
164
+ * Indranil Dutta <duttaindranil497@gmail.com>
165
+ * Itai Steinherz <itaisteinherz@gmail.com>
166
+ * Jelmer van der Ploeg <jelmer@woovar.com>
167
+ * Joël Marty <@joelmarty>
168
+ * John Giannelos <johngiannelos@gmail.com>
169
+ * Joshua Bird <joshua.thomas.bird@gmail.com>
170
+ * Julien Bouquillon <contact@revolunet.com>
171
+ * Julien Lebunetel <julien@lebunetel.com>
172
+ * Kaloneh <kaloneh@gmail.com>
173
+ * Kulshekhar Kabra <@kulshekhar>
174
+ * Lavish Aggarwal <lucky.lavish@gmail.com>
175
+ * Maksym Shalenyi <supamaxy@gmail.com>
176
+ * Manas Mangaonkar <@Pac23>
177
+ * Mansimar Kaur <mansimarkaur.mks@gmail.com>
178
+ * Masataka Takeuchi <masataka.takeuchi@l-is-b.com>
179
+ * Mathieu Agopian <mathieu@agopian.info>
180
+ * Mathieu Leplatre <mathieu@mozilla.com>
181
+ * Matt Boris <mboris@mozilla.com>
182
+ * Maxime Warnier <marmax@gmail.com>
183
+ * Michael Charlton <m.charlton@mac.com>
184
+ * Michiel de Jong <michiel@unhosted.org>
185
+ * Mo Valipour <valipour@gmail.com>
186
+ * Mozillazg
187
+ * Nicolas Hoizey <nicolas@hoizey.com>
188
+ * Nicolas Perriault <nperriault@mozilla.com>
189
+ * Niraj <https://github.com/niraj8>
190
+ * Oron Gola <oron.golar@gmail.com>
191
+ * Palash Nigam <npalash25@gmail.com>
192
+ * Pascal Roessner <roessner.pascal@gmail.com>
193
+ * PeriGK <per.gkolias@gmail.com>
194
+ * Peter Bengtsson <mail@peterbe.com>
195
+ * Peter Rassias <ubcpeter@hotmail.com>
196
+ * realsumit <sumitsarinofficial@gmail.com>
197
+ * Rektide <rektide@voodoowarez.com>
198
+ * Rémy Hubscher <rhubscher@mozilla.com>
199
+ * Renisha Nellums <r.nellums@gmail.com>
200
+ * Ricardo <@rkleine>
201
+ * Rodolphe Quiédeville <rodolphe@quiedeville.org>
202
+ * Sahil Dua <sahildua2305@gmail.com>
203
+ * Sebastian Rodriguez <srodrigu85@gmail.com>
204
+ * Sergey Maranchuk <https://github.com/slav0nic/>
205
+ * Stanisław Wasiutyński <https://github.com/stanley>
206
+ * Stephen Daves <contact@stephendaves.com>
207
+ * Stephen Donner <stephen.donner@gmail.com>
208
+ * Stephen Martin <lockwood@opperline.com>
209
+ * Shweta Oak <oakshweta11@gmail.com>
210
+ * Sofia Utsch <sofia.utsch@gmail.com>
211
+ * Sumit Sarin <sumitsarinofficial@gmail.com>
212
+ * Sunakshi Tejwani <sunakshitejwani@gmail.com>
213
+ * Surya Prashanth <prashantsurya@ymail.com>
214
+ * SwhGo_oN <@swhgoon>
215
+ * Tarek Ziade <tarek@mozilla.com>
216
+ * Taus Brock-Nannestad <taus@semmle.com>
217
+ * Taylor Zane Glaeser <tzglaeser@gmail.com>
218
+ * Thomas Dressler <Thomas.Dressler1@gmail.com>
219
+ * Tiberiu Ichim <@tiberiuichim>
220
+ * Vamsi Sangam <vamsisangam@live.com>
221
+ * Varna Suresh <varna96@gmail.com>
222
+ * Vincent Fretin <@vincentfretin>
223
+ * Vitor Falcao <vitor.falcaor@gmail.com>
224
+ * Wil Clouser <wclouser@mozilla.com>
225
+ * Yann Klis <yann.klis@gmail.com>
226
+ * Jeff Schobelock <jswhatnot15@gmail.com>
227
+ * Shivasheesh Yadav <shivasheeshyadav@gmail.com>
228
+ * Fabian Chong <@feiming>
229
+ * Dan Milgram <danm@intervivo.com>
230
+ * Dex Devlon <@bxff>
231
+ * Varun Koranne <@varun-dhruv>
232
+ * Robin Sharma <robinrythm123@gmail.com>