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,138 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
3
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"is_string",
|
|
9
|
+
"to_list",
|
|
10
|
+
"match_accept_header",
|
|
11
|
+
"ContentTypePredicate",
|
|
12
|
+
"current_service",
|
|
13
|
+
"func_name",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_string(s):
|
|
18
|
+
return isinstance(s, str)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def to_list(obj):
|
|
22
|
+
"""Convert an object to a list if it is not already one"""
|
|
23
|
+
if not isinstance(obj, (list, tuple)):
|
|
24
|
+
obj = [
|
|
25
|
+
obj,
|
|
26
|
+
]
|
|
27
|
+
return obj
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def match_accept_header(func, context, request):
|
|
31
|
+
"""
|
|
32
|
+
Return True if the request ``Accept`` header match
|
|
33
|
+
the list returned by the callable specified in :param:func.
|
|
34
|
+
|
|
35
|
+
Also attach the total list of possible accepted
|
|
36
|
+
egress media types to the request.
|
|
37
|
+
|
|
38
|
+
Utility function for performing content negotiation.
|
|
39
|
+
|
|
40
|
+
:param func:
|
|
41
|
+
The callable returning the list of acceptable
|
|
42
|
+
internet media types for content negotiation.
|
|
43
|
+
It obtains the request object as single argument.
|
|
44
|
+
"""
|
|
45
|
+
acceptable = to_list(func(request))
|
|
46
|
+
request.info["acceptable"] = acceptable
|
|
47
|
+
return len(request.accept.acceptable_offers(acceptable)) > 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def match_content_type_header(func, context, request):
|
|
51
|
+
"""
|
|
52
|
+
Return True if the request ``Content-Type`` header match
|
|
53
|
+
the list returned by the callable specified in :param:func.
|
|
54
|
+
|
|
55
|
+
Also attach the total list of possible accepted
|
|
56
|
+
ingress media types to the request.
|
|
57
|
+
|
|
58
|
+
Utility function for performing request body
|
|
59
|
+
media type checks.
|
|
60
|
+
|
|
61
|
+
:param func:
|
|
62
|
+
The callable returning the list of acceptable
|
|
63
|
+
internet media types for request body
|
|
64
|
+
media type checks.
|
|
65
|
+
It obtains the request object as single argument.
|
|
66
|
+
"""
|
|
67
|
+
supported_contenttypes = to_list(func(request))
|
|
68
|
+
request.info["supported_contenttypes"] = supported_contenttypes
|
|
69
|
+
return content_type_matches(request, supported_contenttypes)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def extract_json_data(request):
|
|
73
|
+
warnings.warn("Use ``cornice.validators.extract_cstruct()`` instead", DeprecationWarning)
|
|
74
|
+
from kinto.core.cornice.validators import extract_cstruct
|
|
75
|
+
|
|
76
|
+
return extract_cstruct(request)["body"]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def extract_form_urlencoded_data(request):
|
|
80
|
+
warnings.warn("Use ``cornice.validators.extract_cstruct()`` instead", DeprecationWarning)
|
|
81
|
+
return request.POST
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def content_type_matches(request, content_types):
|
|
85
|
+
"""
|
|
86
|
+
Check whether ``request.content_type``
|
|
87
|
+
matches given list of content types.
|
|
88
|
+
"""
|
|
89
|
+
return request.content_type in content_types
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ContentTypePredicate(object):
|
|
93
|
+
"""
|
|
94
|
+
Pyramid predicate for matching against ``Content-Type`` request header.
|
|
95
|
+
Should live in ``pyramid.config.predicates``.
|
|
96
|
+
|
|
97
|
+
.. seealso::
|
|
98
|
+
http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/hooks.html
|
|
99
|
+
#view-and-route-predicates
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, val, config):
|
|
103
|
+
self.val = val
|
|
104
|
+
|
|
105
|
+
def text(self):
|
|
106
|
+
return "content_type = %s" % (self.val,)
|
|
107
|
+
|
|
108
|
+
phash = text
|
|
109
|
+
|
|
110
|
+
def __call__(self, context, request):
|
|
111
|
+
return request.content_type == self.val
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def func_name(f):
|
|
115
|
+
"""Return the name of a function or class method."""
|
|
116
|
+
if isinstance(f, str):
|
|
117
|
+
return f
|
|
118
|
+
elif hasattr(f, "__qualname__"): # pragma: no cover
|
|
119
|
+
return f.__qualname__ # Python 3
|
|
120
|
+
elif hasattr(f, "im_class"): # pragma: no cover
|
|
121
|
+
return "{0}.{1}".format(f.im_class.__name__, f.__name__) # Python 2
|
|
122
|
+
else: # pragma: no cover
|
|
123
|
+
return f.__name__ # Python 2
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def current_service(request):
|
|
127
|
+
"""Return the Cornice service matching the specified request.
|
|
128
|
+
|
|
129
|
+
:returns: the service or None if unmatched.
|
|
130
|
+
:rtype: kinto.core.cornice.Service
|
|
131
|
+
"""
|
|
132
|
+
if request.matched_route:
|
|
133
|
+
services = request.registry.cornice_services
|
|
134
|
+
pattern = request.matched_route.pattern
|
|
135
|
+
name = request.matched_route.name
|
|
136
|
+
# try pattern first, then route name else return None
|
|
137
|
+
service = services.get(pattern, services.get("__cornice" + name))
|
|
138
|
+
return service
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
3
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from webob.multidict import MultiDict
|
|
7
|
+
|
|
8
|
+
from kinto.core.cornice.validators._colander import body_validator as colander_body_validator
|
|
9
|
+
from kinto.core.cornice.validators._colander import headers_validator as colander_headers_validator
|
|
10
|
+
from kinto.core.cornice.validators._colander import path_validator as colander_path_validator
|
|
11
|
+
from kinto.core.cornice.validators._colander import (
|
|
12
|
+
querystring_validator as colander_querystring_validator,
|
|
13
|
+
)
|
|
14
|
+
from kinto.core.cornice.validators._colander import validator as colander_validator
|
|
15
|
+
from kinto.core.cornice.validators._marshmallow import body_validator as marshmallow_body_validator
|
|
16
|
+
from kinto.core.cornice.validators._marshmallow import (
|
|
17
|
+
headers_validator as marshmallow_headers_validator,
|
|
18
|
+
)
|
|
19
|
+
from kinto.core.cornice.validators._marshmallow import path_validator as marshmallow_path_validator
|
|
20
|
+
from kinto.core.cornice.validators._marshmallow import (
|
|
21
|
+
querystring_validator as marshmallow_querystring_validator,
|
|
22
|
+
)
|
|
23
|
+
from kinto.core.cornice.validators._marshmallow import validator as marshmallow_validator
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"colander_validator",
|
|
28
|
+
"colander_body_validator",
|
|
29
|
+
"colander_headers_validator",
|
|
30
|
+
"colander_path_validator",
|
|
31
|
+
"colander_querystring_validator",
|
|
32
|
+
"marshmallow_validator",
|
|
33
|
+
"marshmallow_body_validator",
|
|
34
|
+
"marshmallow_headers_validator",
|
|
35
|
+
"marshmallow_path_validator",
|
|
36
|
+
"marshmallow_querystring_validator",
|
|
37
|
+
"extract_cstruct",
|
|
38
|
+
"DEFAULT_VALIDATORS",
|
|
39
|
+
"DEFAULT_FILTERS",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
DEFAULT_VALIDATORS = []
|
|
44
|
+
DEFAULT_FILTERS = []
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def extract_cstruct(request):
|
|
48
|
+
"""
|
|
49
|
+
Extract attributes from the specified `request` such as body, url, path,
|
|
50
|
+
method, querystring, headers, cookies, and returns them in a single dict
|
|
51
|
+
object.
|
|
52
|
+
|
|
53
|
+
:param request: Current request
|
|
54
|
+
:type request: :class:`~pyramid:pyramid.request.Request`
|
|
55
|
+
|
|
56
|
+
:returns: A mapping containing most request attributes.
|
|
57
|
+
:rtype: dict
|
|
58
|
+
"""
|
|
59
|
+
is_json = re.match("^application/(.*?)json$", str(request.content_type))
|
|
60
|
+
|
|
61
|
+
if request.content_type in ("application/x-www-form-urlencoded", "multipart/form-data"):
|
|
62
|
+
body = request.POST.mixed()
|
|
63
|
+
elif request.content_type and not is_json:
|
|
64
|
+
body = request.body
|
|
65
|
+
else:
|
|
66
|
+
if request.body:
|
|
67
|
+
try:
|
|
68
|
+
body = request.json_body
|
|
69
|
+
except ValueError as e:
|
|
70
|
+
request.errors.add("body", "", "Invalid JSON: %s" % e)
|
|
71
|
+
return {}
|
|
72
|
+
else:
|
|
73
|
+
if not hasattr(body, "items") and not isinstance(body, list):
|
|
74
|
+
request.errors.add("body", "", "Should be a JSON object or an array")
|
|
75
|
+
return {}
|
|
76
|
+
else:
|
|
77
|
+
body = {}
|
|
78
|
+
|
|
79
|
+
cstruct = {
|
|
80
|
+
"method": request.method,
|
|
81
|
+
"url": request.url,
|
|
82
|
+
"path": request.matchdict,
|
|
83
|
+
"body": body,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for sub, attr in (("querystring", "GET"), ("header", "headers"), ("cookies", "cookies")):
|
|
87
|
+
data = getattr(request, attr)
|
|
88
|
+
if isinstance(data, MultiDict):
|
|
89
|
+
data = data.mixed()
|
|
90
|
+
else:
|
|
91
|
+
data = dict(data)
|
|
92
|
+
cstruct[sub] = data
|
|
93
|
+
|
|
94
|
+
return cstruct
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
3
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _generate_colander_validator(location):
|
|
10
|
+
"""
|
|
11
|
+
Generate a colander validator for data from the given location.
|
|
12
|
+
|
|
13
|
+
:param location: The location in the request to find the data to be
|
|
14
|
+
validated, such as "body" or "querystring".
|
|
15
|
+
:type location: str
|
|
16
|
+
:return: Returns a callable that will validate the request at the given
|
|
17
|
+
location.
|
|
18
|
+
:rtype: callable
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def _validator(request, schema=None, deserializer=None, **kwargs):
|
|
22
|
+
"""
|
|
23
|
+
Validate the location against the schema defined on the service.
|
|
24
|
+
|
|
25
|
+
The content of the location is deserialized, validated and stored in
|
|
26
|
+
the ``request.validated`` attribute.
|
|
27
|
+
|
|
28
|
+
.. note::
|
|
29
|
+
|
|
30
|
+
If no schema is defined, this validator does nothing.
|
|
31
|
+
Schema should be of type :class:`~colander:colander.MappingSchema`.
|
|
32
|
+
|
|
33
|
+
:param request: Current request
|
|
34
|
+
:type request: :class:`~pyramid:pyramid.request.Request`
|
|
35
|
+
|
|
36
|
+
:param schema: The Colander schema
|
|
37
|
+
:param deserializer: Optional deserializer, defaults to
|
|
38
|
+
:func:`cornice.validators.extract_cstruct`
|
|
39
|
+
"""
|
|
40
|
+
import colander
|
|
41
|
+
|
|
42
|
+
if schema is None:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
schema_instance = _ensure_instantiated(schema)
|
|
46
|
+
|
|
47
|
+
if not isinstance(schema_instance, colander.MappingSchema):
|
|
48
|
+
raise TypeError("Schema should inherit from colander.MappingSchema.")
|
|
49
|
+
|
|
50
|
+
class RequestSchemaMeta(colander._SchemaMeta):
|
|
51
|
+
"""
|
|
52
|
+
A metaclass that will inject a location class attribute into
|
|
53
|
+
RequestSchema.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __new__(cls, name, bases, class_attrs):
|
|
57
|
+
"""
|
|
58
|
+
Instantiate the RequestSchema class.
|
|
59
|
+
|
|
60
|
+
:param name: The name of the class we are instantiating. Will
|
|
61
|
+
be "RequestSchema".
|
|
62
|
+
:type name: str
|
|
63
|
+
:param bases: The class's superclasses.
|
|
64
|
+
:type bases: tuple
|
|
65
|
+
:param dct: The class's class attributes.
|
|
66
|
+
:type dct: dict
|
|
67
|
+
"""
|
|
68
|
+
class_attrs[location] = schema_instance
|
|
69
|
+
return type(name, bases, class_attrs)
|
|
70
|
+
|
|
71
|
+
class RequestSchema(colander.MappingSchema, metaclass=RequestSchemaMeta): # noqa
|
|
72
|
+
"""A schema to validate the request's location attributes."""
|
|
73
|
+
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
validator(request, RequestSchema(), deserializer, **kwargs)
|
|
77
|
+
validated_location = request.validated.get(location, {})
|
|
78
|
+
request.validated.update(validated_location)
|
|
79
|
+
if location not in validated_location:
|
|
80
|
+
request.validated.pop(location, None)
|
|
81
|
+
|
|
82
|
+
return _validator
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
body_validator = _generate_colander_validator("body")
|
|
86
|
+
headers_validator = _generate_colander_validator("headers")
|
|
87
|
+
path_validator = _generate_colander_validator("path")
|
|
88
|
+
querystring_validator = _generate_colander_validator("querystring")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def validator(request, schema=None, deserializer=None, **kwargs):
|
|
92
|
+
"""
|
|
93
|
+
Validate the full request against the schema defined on the service.
|
|
94
|
+
|
|
95
|
+
Each attribute of the request is deserialized, validated and stored in the
|
|
96
|
+
``request.validated`` attribute
|
|
97
|
+
(eg. body in ``request.validated['body']``).
|
|
98
|
+
|
|
99
|
+
.. note::
|
|
100
|
+
|
|
101
|
+
If no schema is defined, this validator does nothing.
|
|
102
|
+
|
|
103
|
+
:param request: Current request
|
|
104
|
+
:type request: :class:`~pyramid:pyramid.request.Request`
|
|
105
|
+
|
|
106
|
+
:param schema: The Colander schema
|
|
107
|
+
:param deserializer: Optional deserializer, defaults to
|
|
108
|
+
:func:`cornice.validators.extract_cstruct`
|
|
109
|
+
"""
|
|
110
|
+
import colander
|
|
111
|
+
|
|
112
|
+
from kinto.core.cornice.validators import extract_cstruct
|
|
113
|
+
|
|
114
|
+
if deserializer is None:
|
|
115
|
+
deserializer = extract_cstruct
|
|
116
|
+
|
|
117
|
+
if schema is None:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
schema = _ensure_instantiated(schema)
|
|
121
|
+
cstruct = deserializer(request)
|
|
122
|
+
try:
|
|
123
|
+
deserialized = schema.deserialize(cstruct)
|
|
124
|
+
except colander.Invalid as e:
|
|
125
|
+
translate = request.localizer.translate
|
|
126
|
+
error_dict = e.asdict(translate=translate)
|
|
127
|
+
for name, msg in error_dict.items():
|
|
128
|
+
location, _, field = name.partition(".")
|
|
129
|
+
request.errors.add(location, field, msg)
|
|
130
|
+
else:
|
|
131
|
+
request.validated.update(deserialized)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _ensure_instantiated(schema):
|
|
135
|
+
if inspect.isclass(schema):
|
|
136
|
+
warnings.warn(
|
|
137
|
+
"Setting schema to a class is deprecated. Set schema to an instance instead.",
|
|
138
|
+
DeprecationWarning,
|
|
139
|
+
stacklevel=2,
|
|
140
|
+
)
|
|
141
|
+
schema = schema()
|
|
142
|
+
return schema
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
3
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _generate_marshmallow_validator(location):
|
|
9
|
+
"""
|
|
10
|
+
Generate a marshmallow validator for data from the given location.
|
|
11
|
+
|
|
12
|
+
:param location: The location in the request to find the data to be
|
|
13
|
+
validated, such as "body" or "querystring".
|
|
14
|
+
:type location: str
|
|
15
|
+
:return: Returns a callable that will validate the request at the given
|
|
16
|
+
location.
|
|
17
|
+
:rtype: callable
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def _validator(request, schema=None, deserializer=None, **kwargs):
|
|
21
|
+
"""
|
|
22
|
+
Validate the location against the schema defined on the service.
|
|
23
|
+
|
|
24
|
+
The content of the location is deserialized, validated and stored in
|
|
25
|
+
the ``request.validated`` attribute.
|
|
26
|
+
|
|
27
|
+
Keyword arguments to be included when initialising the marshmallow
|
|
28
|
+
schema can be passed as a dict in ``kwargs['schema_kwargs']`` variable.
|
|
29
|
+
|
|
30
|
+
.. note::
|
|
31
|
+
|
|
32
|
+
If no schema is defined, this validator does nothing.
|
|
33
|
+
|
|
34
|
+
:param request: Current request
|
|
35
|
+
:type request: :class:`~pyramid:pyramid.request.Request`
|
|
36
|
+
|
|
37
|
+
:param schema: The marshmallow schema
|
|
38
|
+
:param deserializer: Optional deserializer, defaults to
|
|
39
|
+
:func:`cornice.validators.extract_cstruct`
|
|
40
|
+
"""
|
|
41
|
+
import marshmallow
|
|
42
|
+
import marshmallow.schema
|
|
43
|
+
from marshmallow.utils import EXCLUDE
|
|
44
|
+
|
|
45
|
+
if schema is None:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# see if the user wants to set any keyword arguments for their schema
|
|
49
|
+
schema_kwargs = kwargs.get("schema_kwargs", {})
|
|
50
|
+
schema = _instantiate_schema(schema, **schema_kwargs)
|
|
51
|
+
|
|
52
|
+
class ValidatedField(marshmallow.fields.Field):
|
|
53
|
+
def _deserialize(self, value, attr, data, **kwargs):
|
|
54
|
+
schema.context.setdefault("request", request)
|
|
55
|
+
deserialized = schema.load(value)
|
|
56
|
+
return deserialized
|
|
57
|
+
|
|
58
|
+
class Meta(object):
|
|
59
|
+
strict = True
|
|
60
|
+
ordered = True
|
|
61
|
+
unknown = EXCLUDE
|
|
62
|
+
|
|
63
|
+
class RequestSchemaMeta(marshmallow.schema.SchemaMeta):
|
|
64
|
+
"""
|
|
65
|
+
A metaclass that will inject a location class attribute into
|
|
66
|
+
RequestSchema.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __new__(cls, name, bases, class_attrs):
|
|
70
|
+
"""
|
|
71
|
+
Instantiate the RequestSchema class.
|
|
72
|
+
|
|
73
|
+
:param name: The name of the class we are instantiating. Will
|
|
74
|
+
be "RequestSchema".
|
|
75
|
+
:type name: str
|
|
76
|
+
:param bases: The class's superclasses.
|
|
77
|
+
:type bases: tuple
|
|
78
|
+
:param dct: The class's class attributes.
|
|
79
|
+
:type dct: dict
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
class_attrs[location] = ValidatedField(
|
|
83
|
+
required=True, metadata={"load_from": location}
|
|
84
|
+
)
|
|
85
|
+
class_attrs["Meta"] = Meta
|
|
86
|
+
return type(name, bases, class_attrs)
|
|
87
|
+
|
|
88
|
+
class RequestSchema(marshmallow.Schema, metaclass=RequestSchemaMeta): # noqa
|
|
89
|
+
"""A schema to validate the request's location attributes."""
|
|
90
|
+
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
validator(request, RequestSchema, deserializer, **kwargs)
|
|
94
|
+
request.validated = request.validated.get(location, {})
|
|
95
|
+
|
|
96
|
+
return _validator
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
body_validator = _generate_marshmallow_validator("body")
|
|
100
|
+
headers_validator = _generate_marshmallow_validator("header")
|
|
101
|
+
path_validator = _generate_marshmallow_validator("path")
|
|
102
|
+
querystring_validator = _generate_marshmallow_validator("querystring")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _message_normalizer(exc, no_field_name="_schema"):
|
|
106
|
+
"""
|
|
107
|
+
Normally `normalize_messages` will exist on `ValidationError` but pre 2.10
|
|
108
|
+
versions don't have it
|
|
109
|
+
:param exc:
|
|
110
|
+
:param no_field_name:
|
|
111
|
+
:return:
|
|
112
|
+
"""
|
|
113
|
+
if isinstance(exc.messages, dict):
|
|
114
|
+
return exc.messages
|
|
115
|
+
field_names = exc.kwargs.get("field_names", [])
|
|
116
|
+
if len(field_names) == 0:
|
|
117
|
+
return {no_field_name: exc.messages}
|
|
118
|
+
return dict((name, exc.messages) for name in field_names)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def validator(request, schema=None, deserializer=None, **kwargs):
|
|
122
|
+
"""
|
|
123
|
+
Validate the full request against the schema defined on the service.
|
|
124
|
+
|
|
125
|
+
Each attribute of the request is deserialized, validated and stored in the
|
|
126
|
+
``request.validated`` attribute
|
|
127
|
+
(eg. body in ``request.validated['body']``).
|
|
128
|
+
|
|
129
|
+
.. note::
|
|
130
|
+
|
|
131
|
+
If no schema is defined, this validator does nothing.
|
|
132
|
+
|
|
133
|
+
:param request: Current request
|
|
134
|
+
:type request: :class:`~pyramid:pyramid.request.Request`
|
|
135
|
+
|
|
136
|
+
:param schema: The marshmallow schema
|
|
137
|
+
:param deserializer: Optional deserializer, defaults to
|
|
138
|
+
:func:`cornice.validators.extract_cstruct`
|
|
139
|
+
"""
|
|
140
|
+
import marshmallow
|
|
141
|
+
|
|
142
|
+
from kinto.core.cornice.validators import extract_cstruct
|
|
143
|
+
|
|
144
|
+
if deserializer is None:
|
|
145
|
+
deserializer = extract_cstruct
|
|
146
|
+
|
|
147
|
+
if schema is None:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
schema = _instantiate_schema(schema)
|
|
151
|
+
schema.context.setdefault("request", request)
|
|
152
|
+
|
|
153
|
+
cstruct = deserializer(request)
|
|
154
|
+
try:
|
|
155
|
+
deserialized = schema.load(cstruct)
|
|
156
|
+
except marshmallow.ValidationError as err:
|
|
157
|
+
# translate = request.localizer.translate
|
|
158
|
+
normalized_errors = _message_normalizer(err)
|
|
159
|
+
for location, details in normalized_errors.items():
|
|
160
|
+
location = location if location != "_schema" else ""
|
|
161
|
+
if hasattr(details, "items"):
|
|
162
|
+
for subfield, msg in details.items():
|
|
163
|
+
request.errors.add(location, subfield, msg)
|
|
164
|
+
else:
|
|
165
|
+
request.errors.add(location, location, details)
|
|
166
|
+
else:
|
|
167
|
+
request.validated.update(deserialized)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _instantiate_schema(schema, **kwargs):
|
|
171
|
+
"""
|
|
172
|
+
Returns an object of the given marshmallow schema.
|
|
173
|
+
|
|
174
|
+
:param schema: The marshmallow schema class with which the request should
|
|
175
|
+
be validated
|
|
176
|
+
:param kwargs: The keyword arguments that will be provided to the
|
|
177
|
+
marshmallow schema's constructor
|
|
178
|
+
:return: The object of the marshmallow schema
|
|
179
|
+
"""
|
|
180
|
+
if not inspect.isclass(schema):
|
|
181
|
+
raise ValueError("You need to pass Marshmallow class instead of schema instance")
|
|
182
|
+
return schema(**kwargs)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
2
|
+
|
|
3
|
+
from kinto.core.cornice_swagger.swagger import CorniceSwagger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__author__ = """Josip Delic"""
|
|
7
|
+
__email__ = "delicj@delijati.net"
|
|
8
|
+
__version__ = "0.3.0"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["CorniceSwagger"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CorniceSwaggerPredicate(object):
|
|
15
|
+
"""Predicate to add simple information to Cornice Swagger."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, schema, config):
|
|
18
|
+
self.schema = schema
|
|
19
|
+
|
|
20
|
+
def phash(self):
|
|
21
|
+
return str(self.schema)
|
|
22
|
+
|
|
23
|
+
def __call__(self, context, request):
|
|
24
|
+
return self.schema
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def includeme(config):
|
|
28
|
+
# Custom view parameters
|
|
29
|
+
config.add_view_predicate("response_schemas", CorniceSwaggerPredicate)
|
|
30
|
+
config.add_view_predicate("tags", CorniceSwaggerPredicate)
|
|
31
|
+
config.add_view_predicate("operation_id", CorniceSwaggerPredicate)
|
|
32
|
+
config.add_view_predicate("api_security", CorniceSwaggerPredicate)
|
|
33
|
+
config.add_directive("cornice_enable_openapi_view", cornice_enable_openapi_view)
|
|
34
|
+
config.add_directive("cornice_enable_openapi_explorer", cornice_enable_openapi_explorer)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def cornice_enable_openapi_view(
|
|
38
|
+
config,
|
|
39
|
+
api_path="/api-explorer/swagger.json",
|
|
40
|
+
permission=NO_PERMISSION_REQUIRED,
|
|
41
|
+
route_factory=None,
|
|
42
|
+
**kwargs,
|
|
43
|
+
):
|
|
44
|
+
"""
|
|
45
|
+
:param config:
|
|
46
|
+
Pyramid configurator object
|
|
47
|
+
:param api_path:
|
|
48
|
+
where to expose swagger JSON definition view
|
|
49
|
+
:param permission:
|
|
50
|
+
pyramid permission for those views
|
|
51
|
+
:param route_factory:
|
|
52
|
+
factory for context object for those routes
|
|
53
|
+
:param kwargs:
|
|
54
|
+
kwargs that will be passed to CorniceSwagger's `generate()`
|
|
55
|
+
|
|
56
|
+
This registers and configures the view that serves api definitions
|
|
57
|
+
"""
|
|
58
|
+
config.registry.settings["cornice_swagger.spec_kwargs"] = kwargs
|
|
59
|
+
config.add_route("cornice_swagger.open_api_path", api_path, factory=route_factory)
|
|
60
|
+
config.add_view(
|
|
61
|
+
"cornice_swagger.views.open_api_json_view",
|
|
62
|
+
renderer="json",
|
|
63
|
+
permission=permission,
|
|
64
|
+
route_name="cornice_swagger.open_api_path",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def cornice_enable_openapi_explorer(
|
|
69
|
+
config,
|
|
70
|
+
api_explorer_path="/api-explorer",
|
|
71
|
+
permission=NO_PERMISSION_REQUIRED,
|
|
72
|
+
route_factory=None,
|
|
73
|
+
**kwargs,
|
|
74
|
+
):
|
|
75
|
+
"""
|
|
76
|
+
:param config:
|
|
77
|
+
Pyramid configurator object
|
|
78
|
+
:param api_explorer_path:
|
|
79
|
+
where to expose Swagger UI interface view
|
|
80
|
+
:param permission:
|
|
81
|
+
pyramid permission for those views
|
|
82
|
+
:param route_factory:
|
|
83
|
+
factory for context object for those routes
|
|
84
|
+
|
|
85
|
+
This registers and configures the view that serves api explorer
|
|
86
|
+
"""
|
|
87
|
+
config.add_route("cornice_swagger.api_explorer_path", api_explorer_path, factory=route_factory)
|
|
88
|
+
config.add_view(
|
|
89
|
+
"cornice_swagger.views.swagger_ui_template_view",
|
|
90
|
+
permission=permission,
|
|
91
|
+
route_name="cornice_swagger.api_explorer_path",
|
|
92
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module handles the conversion between colander object schemas and swagger
|
|
3
|
+
object schemas.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from kinto.core.cornice_swagger.converters.parameters import ParameterConversionDispatcher
|
|
7
|
+
from kinto.core.cornice_swagger.converters.schema import TypeConversionDispatcher
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def convert_schema(schema_node):
|
|
11
|
+
dispatcher = TypeConversionDispatcher()
|
|
12
|
+
converted = dispatcher(schema_node)
|
|
13
|
+
|
|
14
|
+
return converted
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def convert_parameter(location, schema_node, definition_handler=convert_schema):
|
|
18
|
+
dispatcher = ParameterConversionDispatcher(definition_handler)
|
|
19
|
+
converted = dispatcher(location, schema_node)
|
|
20
|
+
|
|
21
|
+
return converted
|