We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Crash batch list of request has trailing comma
```
echo '{
    "requests": [
        {"path": "/buckets/default/collections/test1/records"},
        {"path": "/buckets/default/collections/test2/records"},
    ]
}' | http POST :8888/v1/batch --auth a:a
```


```
2017-01-18 16:45:58,968 ERROR [kinto.core.views.errors][waitress] "POST  /v1/batch" ? (? ms) '_null' object has no attribute 'get' agent=HTTPie/0.9.2 authn_type=None errno=None exception=Traceback (most recent call last):
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py", line 22, in excview_tween
    response = handler(request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 119, in tm_tween
    reraise(*exc_info)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 98, in tm_tween
    response = handler(request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/router.py", line 158, in handle_request
    view_name
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/view.py", line 547, in _call_view
    response = view_callable(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/config/views.py", line 182,
, in __call__
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 393, in attr_view
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 371, in predicate_wrapper
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 442, in rendered_view
    result = view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 147, in _requestonly_view
    response = view(request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/cornice/service.py", line 484, in wrapper
    validator(request, **args)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/cornice/validators/_colander.py", line 73, in validator
    deserialized = schema.deserialize(cstruct)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py", line 2058, in deserialize
    appstruct = self.typ.deserialize(self, cstruct)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py", line 719, in deserialize
    return self._impl(node, cstruct, callback)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py", line 678, in _impl
    sub_result = callback(subnode, subval)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py", line 717, in callback
    return subnode.deserialize(subcstruct)
  File "/home/mathieu/Code/Mozilla/kinto/kinto/core/views/batch.py", line 57, in deserialize
    defaults = cstruct.get('defaults')
AttributeError: '_null' object has no attribute 'get' lang=None uid=None
2017-01-18 16:45:58,969 INFO  [kinto.core.initialization][waitress] "POST  /v1/batch" 500 (5 ms) request.summary agent=HTTPie/0.9.2 authn_type=None errno=999 lang=None time=2017-01-18T16:45:58 uid=None

```
Crash batch list of request has trailing comma
```
echo '{
    "requests": [
        {"path": "/buckets/default/collections/test1/records"},
        {"path": "/buckets/default/collections/test2/records"},
    ]
}' | http POST :8888/v1/batch --auth a:a
```


```
2017-01-18 16:45:58,968 ERROR [kinto.core.views.errors][waitress] "POST  /v1/batch" ? (? ms) '_null' object has no attribute 'get' agent=HTTPie/0.9.2 authn_type=None errno=None exception=Traceback (most recent call last):
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py", line 22, in excview_tween
    response = handler(request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 119, in tm_tween
    reraise(*exc_info)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 98, in tm_tween
    response = handler(request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/router.py", line 158, in handle_request
    view_name
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/view.py", line 547, in _call_view
    response = view_callable(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/config/views.py", line 182,
, in __call__
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 393, in attr_view
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 371, in predicate_wrapper
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 442, in rendered_view
    result = view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 147, in _requestonly_view
    response = view(request)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/cornice/service.py", line 484, in wrapper
    validator(request, **args)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/cornice/validators/_colander.py", line 73, in validator
    deserialized = schema.deserialize(cstruct)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py", line 2058, in deserialize
    appstruct = self.typ.deserialize(self, cstruct)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py", line 719, in deserialize
    return self._impl(node, cstruct, callback)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py", line 678, in _impl
    sub_result = callback(subnode, subval)
  File "/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py", line 717, in callback
    return subnode.deserialize(subcstruct)
  File "/home/mathieu/Code/Mozilla/kinto/kinto/core/views/batch.py", line 57, in deserialize
    defaults = cstruct.get('defaults')
AttributeError: '_null' object has no attribute 'get' lang=None uid=None
2017-01-18 16:45:58,969 INFO  [kinto.core.initialization][waitress] "POST  /v1/batch" 500 (5 ms) request.summary agent=HTTPie/0.9.2 authn_type=None errno=999 lang=None time=2017-01-18T16:45:58 uid=None

```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kinto/core/views/batch.py`
Content:
```
1 import colander
2 import six
3 
4 from cornice.validators import colander_validator
5 from pyramid import httpexceptions
6 from pyramid.security import NO_PERMISSION_REQUIRED
7 
8 from kinto.core import errors
9 from kinto.core import logger
10 from kinto.core import Service
11 from kinto.core.utils import merge_dicts, build_request, build_response
12 
13 
14 valid_http_method = colander.OneOf(('GET', 'HEAD', 'DELETE', 'TRACE',
15                                     'POST', 'PUT', 'PATCH'))
16 
17 
18 def string_values(node, cstruct):
19     """Validate that a ``colander.Mapping`` only has strings in its values.
20 
21     .. warning::
22 
23         Should be associated to a ``colander.Mapping`` schema node.
24     """
25     are_strings = [isinstance(v, six.string_types) for v in cstruct.values()]
26     if not all(are_strings):
27         error_msg = '%s contains non string value' % cstruct
28         raise colander.Invalid(node, error_msg)
29 
30 
31 class BatchRequestSchema(colander.MappingSchema):
32     method = colander.SchemaNode(colander.String(),
33                                  validator=valid_http_method,
34                                  missing=colander.drop)
35     path = colander.SchemaNode(colander.String(),
36                                validator=colander.Regex('^/'))
37     headers = colander.SchemaNode(colander.Mapping(unknown='preserve'),
38                                   validator=string_values,
39                                   missing=colander.drop)
40     body = colander.SchemaNode(colander.Mapping(unknown='preserve'),
41                                missing=colander.drop)
42 
43 
44 class BatchPayloadSchema(colander.MappingSchema):
45     defaults = BatchRequestSchema(missing=colander.drop).clone()
46     requests = colander.SchemaNode(colander.Sequence(),
47                                    BatchRequestSchema())
48 
49     def __init__(self, *args, **kwargs):
50         super(BatchPayloadSchema, self).__init__(*args, **kwargs)
51         # On defaults, path is not mandatory.
52         self.get('defaults').get('path').missing = colander.drop
53 
54     def deserialize(self, cstruct=colander.null):
55         """Preprocess received data to carefully merge defaults.
56         """
57         defaults = cstruct.get('defaults')
58         requests = cstruct.get('requests')
59         if isinstance(defaults, dict) and isinstance(requests, list):
60             for request in requests:
61                 if isinstance(request, dict):
62                     merge_dicts(request, defaults)
63         return super(BatchPayloadSchema, self).deserialize(cstruct)
64 
65 
66 class BatchRequest(colander.MappingSchema):
67     body = BatchPayloadSchema()
68 
69 
70 batch = Service(name="batch", path='/batch',
71                 description="Batch operations")
72 
73 
74 @batch.post(schema=BatchRequest,
75             validators=(colander_validator,),
76             permission=NO_PERMISSION_REQUIRED)
77 def post_batch(request):
78     requests = request.validated['body']['requests']
79     batch_size = len(requests)
80 
81     limit = request.registry.settings['batch_max_requests']
82     if limit and len(requests) > int(limit):
83         error_msg = 'Number of requests is limited to %s' % limit
84         request.errors.add('body', 'requests', error_msg)
85         return
86 
87     if any([batch.path in req['path'] for req in requests]):
88         error_msg = 'Recursive call on %s endpoint is forbidden.' % batch.path
89         request.errors.add('body', 'requests', error_msg)
90         return
91 
92     responses = []
93 
94     sublogger = logger.new()
95 
96     for subrequest_spec in requests:
97         subrequest = build_request(request, subrequest_spec)
98 
99         sublogger.bind(path=subrequest.path,
100                        method=subrequest.method)
101         try:
102             # Invoke subrequest without individual transaction.
103             resp, subrequest = request.follow_subrequest(subrequest,
104                                                          use_tweens=False)
105         except httpexceptions.HTTPException as e:
106             if e.content_type == 'application/json':
107                 resp = e
108             else:
109                 # JSONify raw Pyramid errors.
110                 resp = errors.http_error(e)
111 
112         sublogger.bind(code=resp.status_code)
113         sublogger.info('subrequest.summary')
114 
115         dict_resp = build_response(resp, subrequest)
116         responses.append(dict_resp)
117 
118     # Rebing batch request for summary
119     logger.bind(path=batch.path,
120                 method=request.method,
121                 batch_size=batch_size,
122                 agent=request.headers.get('User-Agent'),)
123 
124     return {
125         'responses': responses
126     }
127
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kinto/core/views/batch.py b/kinto/core/views/batch.py
--- a/kinto/core/views/batch.py
+++ b/kinto/core/views/batch.py
@@ -54,12 +54,13 @@
     def deserialize(self, cstruct=colander.null):
         """Preprocess received data to carefully merge defaults.
         """
-        defaults = cstruct.get('defaults')
-        requests = cstruct.get('requests')
-        if isinstance(defaults, dict) and isinstance(requests, list):
-            for request in requests:
-                if isinstance(request, dict):
-                    merge_dicts(request, defaults)
+        if cstruct is not colander.null:
+            defaults = cstruct.get('defaults')
+            requests = cstruct.get('requests')
+            if isinstance(defaults, dict) and isinstance(requests, list):
+                for request in requests:
+                    if isinstance(request, dict):
+                        merge_dicts(request, defaults)
         return super(BatchPayloadSchema, self).deserialize(cstruct)

{"golden_diff": "diff --git a/kinto/core/views/batch.py b/kinto/core/views/batch.py\n--- a/kinto/core/views/batch.py\n+++ b/kinto/core/views/batch.py\n@@ -54,12 +54,13 @@\n     def deserialize(self, cstruct=colander.null):\n         \"\"\"Preprocess received data to carefully merge defaults.\n         \"\"\"\n-        defaults = cstruct.get('defaults')\n-        requests = cstruct.get('requests')\n-        if isinstance(defaults, dict) and isinstance(requests, list):\n-            for request in requests:\n-                if isinstance(request, dict):\n-                    merge_dicts(request, defaults)\n+        if cstruct is not colander.null:\n+            defaults = cstruct.get('defaults')\n+            requests = cstruct.get('requests')\n+            if isinstance(defaults, dict) and isinstance(requests, list):\n+                for request in requests:\n+                    if isinstance(request, dict):\n+                        merge_dicts(request, defaults)\n         return super(BatchPayloadSchema, self).deserialize(cstruct)\n", "issue": "Crash batch list of request has trailing comma\n```\r\necho '{\r\n    \"requests\": [\r\n        {\"path\": \"/buckets/default/collections/test1/records\"},\r\n        {\"path\": \"/buckets/default/collections/test2/records\"},\r\n    ]\r\n}' | http POST :8888/v1/batch --auth a:a\r\n```\r\n\r\n\r\n```\r\n2017-01-18 16:45:58,968 ERROR [kinto.core.views.errors][waitress] \"POST  /v1/batch\" ? (? ms) '_null' object has no attribute 'get' agent=HTTPie/0.9.2 authn_type=None errno=None exception=Traceback (most recent call last):\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py\", line 22, in excview_tween\r\n    response = handler(request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py\", line 119, in tm_tween\r\n    reraise(*exc_info)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py\", line 98, in tm_tween\r\n    response = handler(request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/router.py\", line 158, in handle_request\r\n    view_name\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/view.py\", line 547, in _call_view\r\n    response = view_callable(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/config/views.py\", line 182,\r\n, in __call__\r\n    return view(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py\", line 393, in attr_view\r\n    return view(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py\", line 371, in predicate_wrapper\r\n    return view(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py\", line 442, in rendered_view\r\n    result = view(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py\", line 147, in _requestonly_view\r\n    response = view(request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/cornice/service.py\", line 484, in wrapper\r\n    validator(request, **args)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/cornice/validators/_colander.py\", line 73, in validator\r\n    deserialized = schema.deserialize(cstruct)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py\", line 2058, in deserialize\r\n    appstruct = self.typ.deserialize(self, cstruct)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py\", line 719, in deserialize\r\n    return self._impl(node, cstruct, callback)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py\", line 678, in _impl\r\n    sub_result = callback(subnode, subval)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py\", line 717, in callback\r\n    return subnode.deserialize(subcstruct)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/kinto/core/views/batch.py\", line 57, in deserialize\r\n    defaults = cstruct.get('defaults')\r\nAttributeError: '_null' object has no attribute 'get' lang=None uid=None\r\n2017-01-18 16:45:58,969 INFO  [kinto.core.initialization][waitress] \"POST  /v1/batch\" 500 (5 ms) request.summary agent=HTTPie/0.9.2 authn_type=None errno=999 lang=None time=2017-01-18T16:45:58 uid=None\r\n\r\n```\nCrash batch list of request has trailing comma\n```\r\necho '{\r\n    \"requests\": [\r\n        {\"path\": \"/buckets/default/collections/test1/records\"},\r\n        {\"path\": \"/buckets/default/collections/test2/records\"},\r\n    ]\r\n}' | http POST :8888/v1/batch --auth a:a\r\n```\r\n\r\n\r\n```\r\n2017-01-18 16:45:58,968 ERROR [kinto.core.views.errors][waitress] \"POST  /v1/batch\" ? (? ms) '_null' object has no attribute 'get' agent=HTTPie/0.9.2 authn_type=None errno=None exception=Traceback (most recent call last):\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py\", line 22, in excview_tween\r\n    response = handler(request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py\", line 119, in tm_tween\r\n    reraise(*exc_info)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py\", line 98, in tm_tween\r\n    response = handler(request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/router.py\", line 158, in handle_request\r\n    view_name\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/view.py\", line 547, in _call_view\r\n    response = view_callable(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/config/views.py\", line 182,\r\n, in __call__\r\n    return view(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py\", line 393, in attr_view\r\n    return view(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py\", line 371, in predicate_wrapper\r\n    return view(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py\", line 442, in rendered_view\r\n    result = view(context, request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py\", line 147, in _requestonly_view\r\n    response = view(request)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/cornice/service.py\", line 484, in wrapper\r\n    validator(request, **args)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/cornice/validators/_colander.py\", line 73, in validator\r\n    deserialized = schema.deserialize(cstruct)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py\", line 2058, in deserialize\r\n    appstruct = self.typ.deserialize(self, cstruct)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py\", line 719, in deserialize\r\n    return self._impl(node, cstruct, callback)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py\", line 678, in _impl\r\n    sub_result = callback(subnode, subval)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/.venv/local/lib/python2.7/site-packages/colander/__init__.py\", line 717, in callback\r\n    return subnode.deserialize(subcstruct)\r\n  File \"/home/mathieu/Code/Mozilla/kinto/kinto/core/views/batch.py\", line 57, in deserialize\r\n    defaults = cstruct.get('defaults')\r\nAttributeError: '_null' object has no attribute 'get' lang=None uid=None\r\n2017-01-18 16:45:58,969 INFO  [kinto.core.initialization][waitress] \"POST  /v1/batch\" 500 (5 ms) request.summary agent=HTTPie/0.9.2 authn_type=None errno=999 lang=None time=2017-01-18T16:45:58 uid=None\r\n\r\n```\n", "before_files": [{"content": "import colander\nimport six\n\nfrom cornice.validators import colander_validator\nfrom pyramid import httpexceptions\nfrom pyramid.security import NO_PERMISSION_REQUIRED\n\nfrom kinto.core import errors\nfrom kinto.core import logger\nfrom kinto.core import Service\nfrom kinto.core.utils import merge_dicts, build_request, build_response\n\n\nvalid_http_method = colander.OneOf(('GET', 'HEAD', 'DELETE', 'TRACE',\n                                    'POST', 'PUT', 'PATCH'))\n\n\ndef string_values(node, cstruct):\n    \"\"\"Validate that a ``colander.Mapping`` only has strings in its values.\n\n    .. warning::\n\n        Should be associated to a ``colander.Mapping`` schema node.\n    \"\"\"\n    are_strings = [isinstance(v, six.string_types) for v in cstruct.values()]\n    if not all(are_strings):\n        error_msg = '%s contains non string value' % cstruct\n        raise colander.Invalid(node, error_msg)\n\n\nclass BatchRequestSchema(colander.MappingSchema):\n    method = colander.SchemaNode(colander.String(),\n                                 validator=valid_http_method,\n                                 missing=colander.drop)\n    path = colander.SchemaNode(colander.String(),\n                               validator=colander.Regex('^/'))\n    headers = colander.SchemaNode(colander.Mapping(unknown='preserve'),\n                                  validator=string_values,\n                                  missing=colander.drop)\n    body = colander.SchemaNode(colander.Mapping(unknown='preserve'),\n                               missing=colander.drop)\n\n\nclass BatchPayloadSchema(colander.MappingSchema):\n    defaults = BatchRequestSchema(missing=colander.drop).clone()\n    requests = colander.SchemaNode(colander.Sequence(),\n                                   BatchRequestSchema())\n\n    def __init__(self, *args, **kwargs):\n        super(BatchPayloadSchema, self).__init__(*args, **kwargs)\n        # On defaults, path is not mandatory.\n        self.get('defaults').get('path').missing = colander.drop\n\n    def deserialize(self, cstruct=colander.null):\n        \"\"\"Preprocess received data to carefully merge defaults.\n        \"\"\"\n        defaults = cstruct.get('defaults')\n        requests = cstruct.get('requests')\n        if isinstance(defaults, dict) and isinstance(requests, list):\n            for request in requests:\n                if isinstance(request, dict):\n                    merge_dicts(request, defaults)\n        return super(BatchPayloadSchema, self).deserialize(cstruct)\n\n\nclass BatchRequest(colander.MappingSchema):\n    body = BatchPayloadSchema()\n\n\nbatch = Service(name=\"batch\", path='/batch',\n                description=\"Batch operations\")\n\n\n@batch.post(schema=BatchRequest,\n            validators=(colander_validator,),\n            permission=NO_PERMISSION_REQUIRED)\ndef post_batch(request):\n    requests = request.validated['body']['requests']\n    batch_size = len(requests)\n\n    limit = request.registry.settings['batch_max_requests']\n    if limit and len(requests) > int(limit):\n        error_msg = 'Number of requests is limited to %s' % limit\n        request.errors.add('body', 'requests', error_msg)\n        return\n\n    if any([batch.path in req['path'] for req in requests]):\n        error_msg = 'Recursive call on %s endpoint is forbidden.' % batch.path\n        request.errors.add('body', 'requests', error_msg)\n        return\n\n    responses = []\n\n    sublogger = logger.new()\n\n    for subrequest_spec in requests:\n        subrequest = build_request(request, subrequest_spec)\n\n        sublogger.bind(path=subrequest.path,\n                       method=subrequest.method)\n        try:\n            # Invoke subrequest without individual transaction.\n            resp, subrequest = request.follow_subrequest(subrequest,\n                                                         use_tweens=False)\n        except httpexceptions.HTTPException as e:\n            if e.content_type == 'application/json':\n                resp = e\n            else:\n                # JSONify raw Pyramid errors.\n                resp = errors.http_error(e)\n\n        sublogger.bind(code=resp.status_code)\n        sublogger.info('subrequest.summary')\n\n        dict_resp = build_response(resp, subrequest)\n        responses.append(dict_resp)\n\n    # Rebing batch request for summary\n    logger.bind(path=batch.path,\n                method=request.method,\n                batch_size=batch_size,\n                agent=request.headers.get('User-Agent'),)\n\n    return {\n        'responses': responses\n    }\n", "path": "kinto/core/views/batch.py"}], "after_files": [{"content": "import colander\nimport six\n\nfrom cornice.validators import colander_validator\nfrom pyramid import httpexceptions\nfrom pyramid.security import NO_PERMISSION_REQUIRED\n\nfrom kinto.core import errors\nfrom kinto.core import logger\nfrom kinto.core import Service\nfrom kinto.core.utils import merge_dicts, build_request, build_response\n\n\nvalid_http_method = colander.OneOf(('GET', 'HEAD', 'DELETE', 'TRACE',\n                                    'POST', 'PUT', 'PATCH'))\n\n\ndef string_values(node, cstruct):\n    \"\"\"Validate that a ``colander.Mapping`` only has strings in its values.\n\n    .. warning::\n\n        Should be associated to a ``colander.Mapping`` schema node.\n    \"\"\"\n    are_strings = [isinstance(v, six.string_types) for v in cstruct.values()]\n    if not all(are_strings):\n        error_msg = '%s contains non string value' % cstruct\n        raise colander.Invalid(node, error_msg)\n\n\nclass BatchRequestSchema(colander.MappingSchema):\n    method = colander.SchemaNode(colander.String(),\n                                 validator=valid_http_method,\n                                 missing=colander.drop)\n    path = colander.SchemaNode(colander.String(),\n                               validator=colander.Regex('^/'))\n    headers = colander.SchemaNode(colander.Mapping(unknown='preserve'),\n                                  validator=string_values,\n                                  missing=colander.drop)\n    body = colander.SchemaNode(colander.Mapping(unknown='preserve'),\n                               missing=colander.drop)\n\n\nclass BatchPayloadSchema(colander.MappingSchema):\n    defaults = BatchRequestSchema(missing=colander.drop).clone()\n    requests = colander.SchemaNode(colander.Sequence(),\n                                   BatchRequestSchema())\n\n    def __init__(self, *args, **kwargs):\n        super(BatchPayloadSchema, self).__init__(*args, **kwargs)\n        # On defaults, path is not mandatory.\n        self.get('defaults').get('path').missing = colander.drop\n\n    def deserialize(self, cstruct=colander.null):\n        \"\"\"Preprocess received data to carefully merge defaults.\n        \"\"\"\n        if cstruct is not colander.null:\n            defaults = cstruct.get('defaults')\n            requests = cstruct.get('requests')\n            if isinstance(defaults, dict) and isinstance(requests, list):\n                for request in requests:\n                    if isinstance(request, dict):\n                        merge_dicts(request, defaults)\n        return super(BatchPayloadSchema, self).deserialize(cstruct)\n\n\nclass BatchRequest(colander.MappingSchema):\n    body = BatchPayloadSchema()\n\n\nbatch = Service(name=\"batch\", path='/batch',\n                description=\"Batch operations\")\n\n\n@batch.post(schema=BatchRequest,\n            validators=(colander_validator,),\n            permission=NO_PERMISSION_REQUIRED)\ndef post_batch(request):\n    requests = request.validated['body']['requests']\n    batch_size = len(requests)\n\n    limit = request.registry.settings['batch_max_requests']\n    if limit and len(requests) > int(limit):\n        error_msg = 'Number of requests is limited to %s' % limit\n        request.errors.add('body', 'requests', error_msg)\n        return\n\n    if any([batch.path in req['path'] for req in requests]):\n        error_msg = 'Recursive call on %s endpoint is forbidden.' % batch.path\n        request.errors.add('body', 'requests', error_msg)\n        return\n\n    responses = []\n\n    sublogger = logger.new()\n\n    for subrequest_spec in requests:\n        subrequest = build_request(request, subrequest_spec)\n\n        sublogger.bind(path=subrequest.path,\n                       method=subrequest.method)\n        try:\n            # Invoke subrequest without individual transaction.\n            resp, subrequest = request.follow_subrequest(subrequest,\n                                                         use_tweens=False)\n        except httpexceptions.HTTPException as e:\n            if e.content_type == 'application/json':\n                resp = e\n            else:\n                # JSONify raw Pyramid errors.\n                resp = errors.http_error(e)\n\n        sublogger.bind(code=resp.status_code)\n        sublogger.info('subrequest.summary')\n\n        dict_resp = build_response(resp, subrequest)\n        responses.append(dict_resp)\n\n    # Rebing batch request for summary\n    logger.bind(path=batch.path,\n                method=request.method,\n                batch_size=batch_size,\n                agent=request.headers.get('User-Agent'),)\n\n    return {\n        'responses': responses\n    }\n", "path": "kinto/core/views/batch.py"}]}