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,73 @@
1
+ <!-- HTML for static distribution bundle build -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Swagger UI</title>
7
+ <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
8
+ <link rel="stylesheet" type="text/css" href="${ui_css_url}" >
9
+ <style>
10
+ html
11
+ {
12
+ box-sizing: border-box;
13
+ overflow: -moz-scrollbars-vertical;
14
+ overflow-y: scroll;
15
+ }
16
+ *,
17
+ *:before,
18
+ *:after
19
+ {
20
+ box-sizing: inherit;
21
+ }
22
+
23
+ body {
24
+ margin:0;
25
+ background: #fafafa;
26
+ }
27
+ </style>
28
+ </head>
29
+
30
+ <body>
31
+
32
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
33
+ <defs>
34
+ <symbol viewBox="0 0 20 20" id="unlocked">
35
+ <path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
36
+ </symbol>
37
+
38
+ <symbol viewBox="0 0 20 20" id="locked">
39
+ <path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
40
+ </symbol>
41
+
42
+ <symbol viewBox="0 0 20 20" id="close">
43
+ <path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
44
+ </symbol>
45
+
46
+ <symbol viewBox="0 0 20 20" id="large-arrow">
47
+ <path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
48
+ </symbol>
49
+
50
+ <symbol viewBox="0 0 20 20" id="large-arrow-down">
51
+ <path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
52
+ </symbol>
53
+
54
+
55
+ <symbol viewBox="0 0 24 24" id="jump-to">
56
+ <path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
57
+ </symbol>
58
+
59
+ <symbol viewBox="0 0 24 24" id="expand">
60
+ <path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
61
+ </symbol>
62
+
63
+ </defs>
64
+ </svg>
65
+
66
+ <div id="swagger-ui"></div>
67
+
68
+ <script src="${ui_js_bundle_url}"> </script>
69
+ <script src="${ui_js_standalone_url}"> </script>
70
+ ${swagger_ui_script}
71
+ </body>
72
+
73
+ </html>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ window.onload = function() {
3
+
4
+ // Build a system
5
+ const ui = SwaggerUIBundle({
6
+ url: "${swagger_spec_url}",
7
+ dom_id: '#swagger-ui',
8
+ deepLinking: true,
9
+ presets: [
10
+ SwaggerUIBundle.presets.apis,
11
+ SwaggerUIStandalonePreset
12
+ ],
13
+ plugins: [
14
+ SwaggerUIBundle.plugins.DownloadUrl
15
+ ],
16
+ layout: "StandaloneLayout"
17
+ })
18
+
19
+ window.ui = ui
20
+ }
21
+ </script>
@@ -0,0 +1,42 @@
1
+ import colander
2
+
3
+ from kinto.core.cornice.validators import colander_body_validator
4
+
5
+
6
+ def trim(docstring):
7
+ """
8
+ Remove the tabs to spaces, and remove the extra spaces / tabs that are in
9
+ front of the text in docstrings.
10
+
11
+ Implementation taken from http://www.python.org/dev/peps/pep-0257/
12
+ """
13
+ if not docstring:
14
+ return ""
15
+ # Convert tabs to spaces (following the normal Python rules)
16
+ # and split into a list of lines:
17
+ lines = docstring.expandtabs().splitlines()
18
+ lines = [line.strip() for line in lines]
19
+ res = "\n".join(lines)
20
+ return res
21
+
22
+
23
+ def body_schema_transformer(schema, args):
24
+ validators = args.get("validators", [])
25
+ if colander_body_validator in validators:
26
+ body_schema = schema
27
+ schema = colander.MappingSchema()
28
+ schema["body"] = body_schema
29
+ return schema
30
+
31
+
32
+ def merge_dicts(base, changes):
33
+ """Merge b into a recursively, without overwriting values.
34
+
35
+ :param base: the dict that will be altered.
36
+ :param changes: changes to update base.
37
+ """
38
+ for k, v in changes.items():
39
+ if isinstance(v, dict):
40
+ merge_dicts(base.setdefault(k, {}), v)
41
+ else:
42
+ base.setdefault(k, v)
@@ -0,0 +1,78 @@
1
+ import importlib
2
+ from string import Template
3
+
4
+ import cornice
5
+ import cornice_swagger
6
+ import pkg_resources
7
+ from pyramid.response import Response
8
+
9
+
10
+ # hardcode for now since that will work for vast majority of users
11
+ # maybe later add minified resources for behind firewall support?
12
+ ui_css_url = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.23.11/swagger-ui.css"
13
+ ui_js_bundle_url = "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.23.11/swagger-ui-bundle.js"
14
+ ui_js_standalone_url = (
15
+ "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.23.11/swagger-ui-standalone-preset.js"
16
+ )
17
+
18
+
19
+ def swagger_ui_template_view(request):
20
+ """
21
+ Serves Swagger UI page, default Swagger UI config is used but you can
22
+ override the callable that generates the `<script>` tag by setting
23
+ `cornice_swagger.swagger_ui_script_generator` in pyramid config, it defaults
24
+ to 'cornice_swagger.views:swagger_ui_script_template'
25
+
26
+ :param request:
27
+ :return:
28
+ """
29
+ script_generator = request.registry.settings.get(
30
+ "cornice_swagger.swagger_ui_script_generator",
31
+ "cornice_swagger.views:swagger_ui_script_template",
32
+ )
33
+ package, callable = script_generator.split(":")
34
+ imported_package = importlib.import_module(package)
35
+ script_callable = getattr(imported_package, callable)
36
+ template = pkg_resources.resource_string("cornice_swagger", "templates/index.html").decode(
37
+ "utf8"
38
+ )
39
+
40
+ html = Template(template).safe_substitute(
41
+ ui_css_url=ui_css_url,
42
+ ui_js_bundle_url=ui_js_bundle_url,
43
+ ui_js_standalone_url=ui_js_standalone_url,
44
+ swagger_ui_script=script_callable(request),
45
+ )
46
+ return Response(html)
47
+
48
+
49
+ def open_api_json_view(request):
50
+ """
51
+ :param request:
52
+ :return:
53
+
54
+ Generates JSON representation of Swagger spec
55
+ """
56
+ doc = cornice_swagger.CorniceSwagger(
57
+ cornice.service.get_services(), pyramid_registry=request.registry
58
+ )
59
+ kwargs = request.registry.settings["cornice_swagger.spec_kwargs"]
60
+ my_spec = doc.generate(**kwargs)
61
+ return my_spec
62
+
63
+
64
+ def swagger_ui_script_template(request, **kwargs):
65
+ """
66
+ :param request:
67
+ :return:
68
+
69
+ Generates the <script> code that bootstraps Swagger UI, it will be injected
70
+ into index template
71
+ """
72
+ swagger_spec_url = request.route_url("cornice_swagger.open_api_path")
73
+ template = pkg_resources.resource_string(
74
+ "cornice_swagger", "templates/index_script_template.html"
75
+ ).decode("utf8")
76
+ return Template(template).safe_substitute(
77
+ swagger_spec_url=swagger_spec_url,
78
+ )
@@ -0,0 +1,74 @@
1
+ import threading
2
+ import warnings
3
+ from functools import update_wrapper, wraps
4
+
5
+ from pyramid.response import Response
6
+
7
+
8
+ class cache_forever:
9
+ def __init__(self, wrapped):
10
+ self.wrapped = wrapped
11
+ self.saved = None
12
+ self.saved_headers = None
13
+ update_wrapper(self, wrapped)
14
+
15
+ def __call__(self, request, *args, **kwargs):
16
+ if self.saved is None:
17
+ self.saved = self.wrapped(request, *args, **kwargs)
18
+ self.saved_headers = request.response.headers
19
+ if isinstance(self.saved, Response):
20
+ self.saved = None
21
+ raise ValueError("cache_forever cannot cache Response only its body")
22
+
23
+ request.response.write(self.saved)
24
+ request.response.headers.update(**self.saved_headers)
25
+ return request.response
26
+
27
+
28
+ def synchronized(method):
29
+ """Class method decorator to make sure two threads do not execute some code
30
+ at the same time (c.f Java ``synchronized`` keyword).
31
+
32
+ The decorator installs a mutex on the class instance.
33
+ """
34
+
35
+ @wraps(method)
36
+ def decorated(self, *args, **kwargs):
37
+ try:
38
+ lock = getattr(self, "__lock__")
39
+ except AttributeError:
40
+ lock = threading.RLock()
41
+ setattr(self, "__lock__", lock)
42
+
43
+ lock.acquire()
44
+ try:
45
+ result = method(self, *args, **kwargs)
46
+ finally:
47
+ lock.release()
48
+ return result
49
+
50
+ return decorated
51
+
52
+
53
+ def deprecate_kwargs(deprecated):
54
+ """
55
+ A decorator to deprecate keyword arguments.
56
+
57
+ :param dict deprecated: The keywords mapping (old: new)
58
+ """
59
+
60
+ def decorated(func):
61
+ @wraps(func)
62
+ def wrapper(*args, **kwargs):
63
+ new_kwargs = {**kwargs}
64
+ for old_param, new_param in deprecated.items():
65
+ if old_param in kwargs:
66
+ message = f"{func.__qualname__} parameter {old_param!r} is deprecated, use {new_param!r} instead"
67
+ warnings.warn(message, DeprecationWarning)
68
+ new_kwargs[new_param] = new_kwargs.pop(old_param)
69
+
70
+ return func(*args, **new_kwargs)
71
+
72
+ return wrapper
73
+
74
+ return decorated
kinto/core/errors.py ADDED
@@ -0,0 +1,216 @@
1
+ import logging
2
+ from enum import Enum
3
+
4
+ import colander
5
+ from pyramid import httpexceptions
6
+
7
+ from kinto.core.schema import Any
8
+ from kinto.core.utils import json, reapply_cors
9
+
10
+
11
+ class ERRORS(Enum):
12
+ """Predefined errors as specified by the API.
13
+
14
+ +-------------+-------+------------------------------------------------+
15
+ | Status code | Errno | Description |
16
+ +=============+=======+================================================+
17
+ | 401 | 104 | Missing Authorization Token |
18
+ +-------------+-------+------------------------------------------------+
19
+ | 401 | 105 | Invalid Authorization Token |
20
+ +-------------+-------+------------------------------------------------+
21
+ | 400 | 106 | request body was not valid JSON |
22
+ +-------------+-------+------------------------------------------------+
23
+ | 400 | 107 | invalid request parameter |
24
+ +-------------+-------+------------------------------------------------+
25
+ | 400 | 108 | missing request parameter |
26
+ +-------------+-------+------------------------------------------------+
27
+ | 400 | 109 | invalid posted data |
28
+ +-------------+-------+------------------------------------------------+
29
+ | 404 | 110 | Invalid Token / id |
30
+ +-------------+-------+------------------------------------------------+
31
+ | 404 | 111 | Missing Token / id |
32
+ +-------------+-------+------------------------------------------------+
33
+ | 411 | 112 | Content-Length header was not provided |
34
+ +-------------+-------+------------------------------------------------+
35
+ | 413 | 113 | Request body too large |
36
+ +-------------+-------+------------------------------------------------+
37
+ | 412 | 114 | Resource was modified meanwhile |
38
+ +-------------+-------+------------------------------------------------+
39
+ | 405 | 115 | Method not allowed on this end point |
40
+ +-------------+-------+------------------------------------------------+
41
+ | 404 | 116 | Requested version not available on this server |
42
+ +-------------+-------+------------------------------------------------+
43
+ | 429 | 117 | Client has sent too many requests |
44
+ +-------------+-------+------------------------------------------------+
45
+ | 403 | 121 | Resource's access forbidden for this user |
46
+ +-------------+-------+------------------------------------------------+
47
+ | 409 | 122 | Another resource violates constraint |
48
+ +-------------+-------+------------------------------------------------+
49
+ | 500 | 999 | Internal Server Error |
50
+ +-------------+-------+------------------------------------------------+
51
+ | 503 | 201 | Service Temporary unavailable due to high load |
52
+ +-------------+-------+------------------------------------------------+
53
+ | 410 | 202 | Service deprecated |
54
+ +-------------+-------+------------------------------------------------+
55
+ """
56
+
57
+ MISSING_AUTH_TOKEN = 104
58
+ INVALID_AUTH_TOKEN = 105
59
+ BADJSON = 106
60
+ INVALID_PARAMETERS = 107
61
+ MISSING_PARAMETERS = 108
62
+ INVALID_POSTED_DATA = 109
63
+ INVALID_RESOURCE_ID = 110
64
+ MISSING_RESOURCE = 111
65
+ MISSING_CONTENT_LENGTH = 112
66
+ REQUEST_TOO_LARGE = 113
67
+ MODIFIED_MEANWHILE = 114
68
+ METHOD_NOT_ALLOWED = 115
69
+ VERSION_NOT_AVAILABLE = 116
70
+ CLIENT_REACHED_CAPACITY = 117
71
+ FORBIDDEN = 121
72
+ CONSTRAINT_VIOLATED = 122
73
+ UNDEFINED = 999
74
+ BACKEND = 201
75
+ SERVICE_DEPRECATED = 202
76
+
77
+
78
+ class ErrorSchema(colander.MappingSchema):
79
+ """Payload schema for Kinto errors."""
80
+
81
+ code = colander.SchemaNode(colander.Integer())
82
+ errno = colander.SchemaNode(colander.Integer())
83
+ error = colander.SchemaNode(colander.String())
84
+ message = colander.SchemaNode(colander.String(), missing=colander.drop)
85
+ info = colander.SchemaNode(colander.String(), missing=colander.drop)
86
+ details = colander.SchemaNode(Any(), missing=colander.drop)
87
+
88
+
89
+ def http_error(
90
+ httpexception, errno=None, code=None, error=None, message=None, info=None, details=None
91
+ ):
92
+ """Return a JSON formated response matching the error HTTP API.
93
+
94
+ :param httpexception: Instance of :mod:`~pyramid:pyramid.httpexceptions`
95
+ :param errno: stable application-level error number (e.g. 109)
96
+ :param code: matches the HTTP status code (e.g 400)
97
+ :param error: string description of error type (e.g. "Bad request")
98
+ :param message: context information (e.g. "Invalid request parameters")
99
+ :param info: information about error (e.g. URL to troubleshooting)
100
+ :param details: additional structured details (conflicting object)
101
+ :returns: the formatted response object
102
+ :rtype: pyramid.httpexceptions.HTTPException
103
+ """
104
+ errno = errno or ERRORS.UNDEFINED
105
+
106
+ if isinstance(errno, Enum):
107
+ errno = errno.value
108
+
109
+ body = {
110
+ "code": code or httpexception.code,
111
+ "errno": errno,
112
+ "error": error or httpexception.title,
113
+ "message": message,
114
+ "info": info,
115
+ "details": details or colander.drop,
116
+ }
117
+
118
+ response = httpexception
119
+ response.errno = errno
120
+ response.json = ErrorSchema().deserialize(body)
121
+ response.content_type = "application/json"
122
+ return response
123
+
124
+
125
+ def json_error_handler(request):
126
+ """Cornice JSON error handler, returning consistant JSON formatted errors
127
+ from schema validation errors.
128
+
129
+ This is meant to be used is custom services in your applications.
130
+
131
+ .. code-block:: python
132
+
133
+ upload = Service(name="upload", path='/upload',
134
+ error_handler=errors.json_error_handler)
135
+
136
+ .. warning::
137
+
138
+ Only the first error of the list is formatted in the response.
139
+ (c.f. HTTP API).
140
+ """
141
+ errors = request.errors
142
+ sorted_errors = sorted(errors, key=lambda x: str(x["name"]))
143
+ for error in sorted_errors:
144
+ # Decode in place.
145
+ if isinstance(error["description"], bytes):
146
+ error["description"] = error["description"].decode("utf-8")
147
+
148
+ # In Cornice, we call error handler if at least one error was set.
149
+ error = sorted_errors[0]
150
+ name = error["name"]
151
+ description = error["description"]
152
+
153
+ if name is not None:
154
+ if str(name) in description:
155
+ message = description
156
+ else:
157
+ message = "{name} in {location}: {description}".format_map(error)
158
+ else:
159
+ message = "{location}: {description}".format_map(error)
160
+
161
+ response = http_error(
162
+ httpexceptions.HTTPBadRequest(),
163
+ code=errors.status,
164
+ errno=ERRORS.INVALID_PARAMETERS.value,
165
+ error="Invalid parameters",
166
+ message=message,
167
+ details=sorted_errors,
168
+ )
169
+ response.status = errors.status
170
+ response = reapply_cors(request, response)
171
+ return response
172
+
173
+
174
+ def raise_invalid(request, location="body", name=None, description=None, **kwargs):
175
+ """Helper to raise a validation error.
176
+
177
+ :param location: location in request (e.g. ``'querystring'``)
178
+ :param name: field name
179
+ :param description: detailed description of validation error
180
+
181
+ :raises: :class:`~pyramid:pyramid.httpexceptions.HTTPBadRequest`
182
+ """
183
+ request.errors.add(location, name, description, **kwargs)
184
+ response = json_error_handler(request)
185
+ raise response
186
+
187
+
188
+ def send_alert(request, message=None, url=None, code="soft-eol"):
189
+ """Helper to add an Alert header to the response.
190
+
191
+ :param code: The type of error 'soft-eol', 'hard-eol'
192
+ :param message: The description message.
193
+ :param url: The URL for more information, default to the documentation url.
194
+ """
195
+ if url is None:
196
+ url = request.registry.settings["project_docs"]
197
+
198
+ request.response.headers["Alert"] = json.dumps({"code": code, "message": message, "url": url})
199
+
200
+
201
+ def request_GET(request):
202
+ """Catches a UnicodeDecode error in request.GET in case a wrong request was received.
203
+ Fixing a webob long term issue: https://github.com/Pylons/webob/issues/161
204
+ """
205
+ try:
206
+ return request.GET
207
+ except UnicodeDecodeError:
208
+ querystring = request.environ.get("QUERY_STRING", "")
209
+ logger = logging.getLogger(__name__)
210
+ logger.info("Error decoding QUERY_STRING: %s" % request.environ)
211
+ raise http_error(
212
+ httpexceptions.HTTPBadRequest(),
213
+ errno=ERRORS.INVALID_PARAMETERS,
214
+ message="A request with an incorrect encoding in the querystring was"
215
+ "received. Please make sure your requests are encoded in UTF-8: %s" % querystring,
216
+ )