prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  IntegrityError if you try to create two records with the same last_modified value in a batch
  ```
  demo:
    collections:
      demo:
        records:
          abc:
            data:
              last_modified: 123
          efg:
            data:
              last_modified: 123
  ```
  
  ```
  $ kinto-wizard load -s https://kinto.dev.mozaws.net/v1 -a admin:admin  demo.yaml --force -b demo -c demo
  kinto_http.exceptions.KintoException: POST /v1/batch - 503 503 - 
  {'message': 'Service temporary unavailable due to overloading or maintenance, please retry later.',
   'code': 503, 'errno': 201, 'error': 'Service Unavailable'}
  ```
  
  --- 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 logging
  2 
  3 import colander
  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 Service
  10 from kinto.core.errors import ErrorSchema
  11 from kinto.core.utils import merge_dicts, build_request, build_response
  12 
  13 
  14 subrequest_logger = logging.getLogger('subrequest.summary')
  15 
  16 valid_http_method = colander.OneOf(('GET', 'HEAD', 'DELETE', 'TRACE',
  17                                     'POST', 'PUT', 'PATCH'))
  18 
  19 
  20 def string_values(node, cstruct):
  21     """Validate that a ``colander.Mapping`` only has strings in its values.
  22 
  23     .. warning::
  24 
  25         Should be associated to a ``colander.Mapping`` schema node.
  26     """
  27     are_strings = [isinstance(v, str) for v in cstruct.values()]
  28     if not all(are_strings):
  29         error_msg = '{} contains non string value'.format(cstruct)
  30         raise colander.Invalid(node, error_msg)
  31 
  32 
  33 class BatchRequestSchema(colander.MappingSchema):
  34     method = colander.SchemaNode(colander.String(),
  35                                  validator=valid_http_method,
  36                                  missing=colander.drop)
  37     path = colander.SchemaNode(colander.String(),
  38                                validator=colander.Regex('^/'))
  39     headers = colander.SchemaNode(colander.Mapping(unknown='preserve'),
  40                                   validator=string_values,
  41                                   missing=colander.drop)
  42     body = colander.SchemaNode(colander.Mapping(unknown='preserve'),
  43                                missing=colander.drop)
  44 
  45     @staticmethod
  46     def schema_type():
  47         return colander.Mapping(unknown='raise')
  48 
  49 
  50 class BatchPayloadSchema(colander.MappingSchema):
  51     defaults = BatchRequestSchema(missing=colander.drop).clone()
  52     requests = colander.SchemaNode(colander.Sequence(),
  53                                    BatchRequestSchema())
  54 
  55     @staticmethod
  56     def schema_type():
  57         return colander.Mapping(unknown='raise')
  58 
  59     def __init__(self, *args, **kwargs):
  60         super().__init__(*args, **kwargs)
  61         # On defaults, path is not mandatory.
  62         self.get('defaults').get('path').missing = colander.drop
  63 
  64     def deserialize(self, cstruct=colander.null):
  65         """Preprocess received data to carefully merge defaults.
  66         """
  67         if cstruct is not colander.null:
  68             defaults = cstruct.get('defaults')
  69             requests = cstruct.get('requests')
  70             if isinstance(defaults, dict) and isinstance(requests, list):
  71                 for request in requests:
  72                     if isinstance(request, dict):
  73                         merge_dicts(request, defaults)
  74         return super().deserialize(cstruct)
  75 
  76 
  77 class BatchRequest(colander.MappingSchema):
  78     body = BatchPayloadSchema()
  79 
  80 
  81 class BatchResponseSchema(colander.MappingSchema):
  82     status = colander.SchemaNode(colander.Integer())
  83     path = colander.SchemaNode(colander.String())
  84     headers = colander.SchemaNode(colander.Mapping(unknown='preserve'),
  85                                   validator=string_values,
  86                                   missing=colander.drop)
  87     body = colander.SchemaNode(colander.Mapping(unknown='preserve'),
  88                                missing=colander.drop)
  89 
  90 
  91 class BatchResponseBodySchema(colander.MappingSchema):
  92     responses = colander.SequenceSchema(BatchResponseSchema(missing=colander.drop))
  93 
  94 
  95 class BatchResponse(colander.MappingSchema):
  96     body = BatchResponseBodySchema()
  97 
  98 
  99 class ErrorResponseSchema(colander.MappingSchema):
  100     body = ErrorSchema()
  101 
  102 
  103 batch_responses = {
  104     '200': BatchResponse(description='Return a list of operation responses.'),
  105     '400': ErrorResponseSchema(description='The request was badly formatted.'),
  106     'default': ErrorResponseSchema(description='an unknown error occurred.')
  107 }
  108 
  109 batch = Service(name='batch', path='/batch',
  110                 description='Batch operations')
  111 
  112 
  113 @batch.post(schema=BatchRequest,
  114             validators=(colander_validator,),
  115             permission=NO_PERMISSION_REQUIRED,
  116             tags=['Batch'], operation_id='batch',
  117             response_schemas=batch_responses)
  118 def post_batch(request):
  119     requests = request.validated['body']['requests']
  120 
  121     request.log_context(batch_size=len(requests))
  122 
  123     limit = request.registry.settings['batch_max_requests']
  124     if limit and len(requests) > int(limit):
  125         error_msg = 'Number of requests is limited to {}'.format(limit)
  126         request.errors.add('body', 'requests', error_msg)
  127         return
  128 
  129     if any([batch.path in req['path'] for req in requests]):
  130         error_msg = 'Recursive call on {} endpoint is forbidden.'.format(batch.path)
  131         request.errors.add('body', 'requests', error_msg)
  132         return
  133 
  134     responses = []
  135 
  136     for subrequest_spec in requests:
  137         subrequest = build_request(request, subrequest_spec)
  138 
  139         log_context = {**request.log_context(),
  140                        'path': subrequest.path,
  141                        'method': subrequest.method}
  142         try:
  143             # Invoke subrequest without individual transaction.
  144             resp, subrequest = request.follow_subrequest(subrequest,
  145                                                          use_tweens=False)
  146         except httpexceptions.HTTPException as e:
  147             if e.content_type == 'application/json':
  148                 resp = e
  149             else:
  150                 # JSONify raw Pyramid errors.
  151                 resp = errors.http_error(e)
  152 
  153         subrequest_logger.info('subrequest.summary', extra=log_context)
  154 
  155         dict_resp = build_response(resp, subrequest)
  156         responses.append(dict_resp)
  157 
  158     return {
  159         'responses': responses
  160     }
  161
  ```
  --- 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()
  
  ```
  
golden_diff: 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
  @@ -144,6 +144,13 @@
               resp, subrequest = request.follow_subrequest(subrequest,
                                                            use_tweens=False)
           except httpexceptions.HTTPException as e:
  +            # Since some request in the batch failed, we need to stop the parent request
  +            # through Pyramid's transaction manager. 5XX errors are already caught by
  +            # pyramid_tm's commit_veto
  +            # https://github.com/Kinto/kinto/issues/624
  +            if e.status_code == 409:
  +                request.tm.abort()
  +
               if e.content_type == 'application/json':
                   resp = e
               else:
  
verification_info: {"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@@ -144,6 +144,13 @@\n             resp, subrequest = request.follow_subrequest(subrequest,\n                                                          use_tweens=False)\n         except httpexceptions.HTTPException as e:\n+            # Since some request in the batch failed, we need to stop the parent request\n+            # through Pyramid's transaction manager. 5XX errors are already caught by\n+            # pyramid_tm's commit_veto\n+            # https://github.com/Kinto/kinto/issues/624\n+            if e.status_code == 409:\n+                request.tm.abort()\n+\n             if e.content_type == 'application/json':\n                 resp = e\n             else:\n", "issue": "IntegrityError if you try to create two records with the same last_modified value in a batch\n```\r\ndemo:\r\n  collections:\r\n    demo:\r\n      records:\r\n        abc:\r\n          data:\r\n            last_modified: 123\r\n        efg:\r\n          data:\r\n            last_modified: 123\r\n```\r\n\r\n```\r\n$ kinto-wizard load -s https://kinto.dev.mozaws.net/v1 -a admin:admin  demo.yaml --force -b demo -c demo\r\nkinto_http.exceptions.KintoException: POST /v1/batch - 503 503 - \r\n{'message': 'Service temporary unavailable due to overloading or maintenance, please retry later.',\r\n 'code': 503, 'errno': 201, 'error': 'Service Unavailable'}\r\n```\n", "before_files": [{"content": "import logging\n\nimport colander\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 Service\nfrom kinto.core.errors import ErrorSchema\nfrom kinto.core.utils import merge_dicts, build_request, build_response\n\n\nsubrequest_logger = logging.getLogger('subrequest.summary')\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, str) for v in cstruct.values()]\n    if not all(are_strings):\n        error_msg = '{} contains non string value'.format(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    @staticmethod\n    def schema_type():\n        return colander.Mapping(unknown='raise')\n\n\nclass BatchPayloadSchema(colander.MappingSchema):\n    defaults = BatchRequestSchema(missing=colander.drop).clone()\n    requests = colander.SchemaNode(colander.Sequence(),\n                                   BatchRequestSchema())\n\n    @staticmethod\n    def schema_type():\n        return colander.Mapping(unknown='raise')\n\n    def __init__(self, *args, **kwargs):\n        super().__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().deserialize(cstruct)\n\n\nclass BatchRequest(colander.MappingSchema):\n    body = BatchPayloadSchema()\n\n\nclass BatchResponseSchema(colander.MappingSchema):\n    status = colander.SchemaNode(colander.Integer())\n    path = colander.SchemaNode(colander.String())\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 BatchResponseBodySchema(colander.MappingSchema):\n    responses = colander.SequenceSchema(BatchResponseSchema(missing=colander.drop))\n\n\nclass BatchResponse(colander.MappingSchema):\n    body = BatchResponseBodySchema()\n\n\nclass ErrorResponseSchema(colander.MappingSchema):\n    body = ErrorSchema()\n\n\nbatch_responses = {\n    '200': BatchResponse(description='Return a list of operation responses.'),\n    '400': ErrorResponseSchema(description='The request was badly formatted.'),\n    'default': ErrorResponseSchema(description='an unknown error occurred.')\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,\n            tags=['Batch'], operation_id='batch',\n            response_schemas=batch_responses)\ndef post_batch(request):\n    requests = request.validated['body']['requests']\n\n    request.log_context(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 {}'.format(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 {} endpoint is forbidden.'.format(batch.path)\n        request.errors.add('body', 'requests', error_msg)\n        return\n\n    responses = []\n\n    for subrequest_spec in requests:\n        subrequest = build_request(request, subrequest_spec)\n\n        log_context = {**request.log_context(),\n                       '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        subrequest_logger.info('subrequest.summary', extra=log_context)\n\n        dict_resp = build_response(resp, subrequest)\n        responses.append(dict_resp)\n\n    return {\n        'responses': responses\n    }\n", "path": "kinto/core/views/batch.py"}], "after_files": [{"content": "import logging\n\nimport colander\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 Service\nfrom kinto.core.errors import ErrorSchema\nfrom kinto.core.utils import merge_dicts, build_request, build_response\n\n\nsubrequest_logger = logging.getLogger('subrequest.summary')\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, str) for v in cstruct.values()]\n    if not all(are_strings):\n        error_msg = '{} contains non string value'.format(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    @staticmethod\n    def schema_type():\n        return colander.Mapping(unknown='raise')\n\n\nclass BatchPayloadSchema(colander.MappingSchema):\n    defaults = BatchRequestSchema(missing=colander.drop).clone()\n    requests = colander.SchemaNode(colander.Sequence(),\n                                   BatchRequestSchema())\n\n    @staticmethod\n    def schema_type():\n        return colander.Mapping(unknown='raise')\n\n    def __init__(self, *args, **kwargs):\n        super().__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().deserialize(cstruct)\n\n\nclass BatchRequest(colander.MappingSchema):\n    body = BatchPayloadSchema()\n\n\nclass BatchResponseSchema(colander.MappingSchema):\n    status = colander.SchemaNode(colander.Integer())\n    path = colander.SchemaNode(colander.String())\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 BatchResponseBodySchema(colander.MappingSchema):\n    responses = colander.SequenceSchema(BatchResponseSchema(missing=colander.drop))\n\n\nclass BatchResponse(colander.MappingSchema):\n    body = BatchResponseBodySchema()\n\n\nclass ErrorResponseSchema(colander.MappingSchema):\n    body = ErrorSchema()\n\n\nbatch_responses = {\n    '200': BatchResponse(description='Return a list of operation responses.'),\n    '400': ErrorResponseSchema(description='The request was badly formatted.'),\n    'default': ErrorResponseSchema(description='an unknown error occurred.')\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,\n            tags=['Batch'], operation_id='batch',\n            response_schemas=batch_responses)\ndef post_batch(request):\n    requests = request.validated['body']['requests']\n\n    request.log_context(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 {}'.format(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 {} endpoint is forbidden.'.format(batch.path)\n        request.errors.add('body', 'requests', error_msg)\n        return\n\n    responses = []\n\n    for subrequest_spec in requests:\n        subrequest = build_request(request, subrequest_spec)\n\n        log_context = {**request.log_context(),\n                       '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            # Since some request in the batch failed, we need to stop the parent request\n            # through Pyramid's transaction manager. 5XX errors are already caught by\n            # pyramid_tm's commit_veto\n            # https://github.com/Kinto/kinto/issues/624\n            if e.status_code == 409:\n                request.tm.abort()\n\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        subrequest_logger.info('subrequest.summary', extra=log_context)\n\n        dict_resp = build_response(resp, subrequest)\n        responses.append(dict_resp)\n\n    return {\n        'responses': responses\n    }\n", "path": "kinto/core/views/batch.py"}]}