internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: Kinto/kinto

Issue:
GET on /v1/admin returns 404
The expected address for built-in admin plugin is `/v1/admin/`. But if you forget the trailing slash and type `/v1/admin` you get a 404. I think it would be better to raise a 307.

Related to #112 and #858.

```
> http get localhost:8888/v1/admin/
HTTP/1.1 200 OK
(...)

> http get localhost:8888/v1/admin
HTTP/1.1 404 Not Found
(...)
```


GET on /v1/admin returns 404
The expected address for built-in admin plugin is `/v1/admin/`. But if you forget the trailing slash and type `/v1/admin` you get a 404. I think it would be better to raise a 307.

Related to #112 and #858.

```
> http get localhost:8888/v1/admin/
HTTP/1.1 200 OK
(...)

> http get localhost:8888/v1/admin
HTTP/1.1 404 Not Found
(...)
```




Discussion:
I totally agree :)
I totally agree :)

Resolving patch:
diff --git a/kinto/plugins/admin/__init__.py b/kinto/plugins/admin/__init__.py
--- a/kinto/plugins/admin/__init__.py
+++ b/kinto/plugins/admin/__init__.py
@@ -1,4 +1,5 @@
 from pyramid.static import static_view
+from pyramid.httpexceptions import HTTPTemporaryRedirect
 
 
 def includeme(config):
@@ -15,3 +16,10 @@ def includeme(config):
     build_dir = static_view('kinto.plugins.admin:build', use_subpath=True)
     config.add_route('catchall_static', '/admin/*subpath')
     config.add_view(build_dir, route_name="catchall_static")
+
+    # Setup redirect without trailing slash.
+    def admin_redirect_view(request):
+        raise HTTPTemporaryRedirect(request.path + '/')
+
+    config.add_route('admin_redirect', '/admin')
+    config.add_view(admin_redirect_view, route_name="admin_redirect")


Test patch:
diff --git a/tests/plugins/test_admin.py b/tests/plugins/test_admin.py
--- a/tests/plugins/test_admin.py
+++ b/tests/plugins/test_admin.py
@@ -26,3 +26,7 @@ def test_admin_index_cat_be_reached(self):
         self.maxDiff = None
         resp = self.app.get('/admin/')
         assert "html" in resp.body.decode('utf-8')
+
+    def test_admin_redirect_without_trailing_slash(self):
+        resp = self.app.get('/admin', status=307)
+        self.assertTrue(resp.headers['location'].endswith('/admin/'))
Repository: Kinto/kinto

Issue:
Kinto Admin version not shown in footer

![screenshot from 2017-01-24 17-01-08](https://cloud.githubusercontent.com/assets/546692/22255110/b87c08e2-e256-11e6-9880-b85014bb9f0a.png)

Looks like the effects of this are lost when packaged as a kinto plugin:

https://github.com/Kinto/kinto-admin/commit/8b184b041121ed4affddfbc2ce98ce658226ee34


Discussion:
The admin must be built exposing a KINTO_ADMIN_VERSION env var, eg. 

```
$ KINTO_ADMIN_VERSION=1.8.0 npm run build
```

Resolving patch:
diff --git a/kinto/plugins/admin/__init__.py b/kinto/plugins/admin/__init__.py
--- a/kinto/plugins/admin/__init__.py
+++ b/kinto/plugins/admin/__init__.py
@@ -8,7 +8,7 @@ def includeme(config):
     # Expose capability.
     config.add_api_capability(
         "admin",
-        version="1.8.0",
+        version="1.8.1",
         description="Serves the admin console.",
         url="https://github.com/Kinto/kinto-admin/",
     )


Test patch:
Repository: Kinto/kinto

Issue:
Retry for cache backend does not catch SQLAlchemy errors
It only catches `psycopg2.IntegrityError` ...
Retry for cache backend does not catch SQLAlchemy errors
It only catches `psycopg2.IntegrityError` ...


Discussion:

Resolving patch:
diff --git a/kinto/core/cache/postgresql/__init__.py b/kinto/core/cache/postgresql/__init__.py
--- a/kinto/core/cache/postgresql/__init__.py
+++ b/kinto/core/cache/postgresql/__init__.py
@@ -16,23 +16,18 @@
 
 
 def retry_on_failure(func):
-    try:
-        import psycopg2
-    except ImportError:  # pragma: no cover
-        pass  # Do not break (but will fail nicely later anyway)
-
     @wraps(func)
     def wraps_func(self, *args, **kwargs):
         tries = kwargs.pop('tries', 0)
         try:
             return func(self, *args, **kwargs)
-        except psycopg2.IntegrityError as e:
+        except BackendError as e:
             if tries < MAX_RETRIES:
                 # Skip delay the 2 first times.
                 delay = max(0, tries - 1) * DELAY_BETWEEN_RETRIES_IN_SECONDS
                 time.sleep(delay)
                 return wraps_func(self, tries=(tries + 1), *args, **kwargs)
-            raise BackendError(original=e)
+            raise e
     return wraps_func
 
 


Test patch:
diff --git a/tests/core/test_cache.py b/tests/core/test_cache.py
--- a/tests/core/test_cache.py
+++ b/tests/core/test_cache.py
@@ -96,17 +96,15 @@ def setUp(self):
             side_effect=sqlalchemy.exc.SQLAlchemyError)
 
     def test_retry_on_integrity_error(self):
-        import psycopg2
         connect = mock.MagicMock()
-        connect.__enter__.return_value.execute.side_effect = [psycopg2.IntegrityError, None]
+        connect.__enter__.return_value.execute.side_effect = [BackendError, None]
         with mock.patch.object(self.cache.client, 'connect', return_value=connect):
             self.cache.set('this[]', 'value')
             assert connect.__enter__.return_value.execute.call_count == 2
 
     def test_retry_on_integrity_error_and_raises_after_a_few_tries(self):
-        import psycopg2
         connect = mock.MagicMock()
-        connect.__enter__.return_value.execute.side_effect = psycopg2.IntegrityError
+        connect.__enter__.return_value.execute.side_effect = BackendError
         with mock.patch.object(self.cache.client, 'connect', return_value=connect):
             with self.assertRaises(BackendError):
                 self.cache.set('this[]', 'value')
Repository: Kinto/kinto

Issue:
IntegrityError with Posgresql cache backend
I would like to update the current request with one of http://stackoverflow.com/a/1109198/186202


Discussion:

Resolving patch:
diff --git a/kinto/core/cache/postgresql/__init__.py b/kinto/core/cache/postgresql/__init__.py
--- a/kinto/core/cache/postgresql/__init__.py
+++ b/kinto/core/cache/postgresql/__init__.py
@@ -148,19 +148,31 @@ def expire(self, key, ttl):
     def set(self, key, value, ttl=None):
         if ttl is None:
             logger.warning("No TTL for cache key %r" % key)
+        # Query for PostgreSQL 9.5+
         query = """
+        INSERT INTO cache (key, value, ttl)
+        VALUES (:key, :value, sec2ttl(:ttl))
+        ON CONFLICT (key) DO
+        UPDATE SET value = :value, ttl = sec2ttl(:ttl);
+        """
+        # Query for PostgreSQL 9.4
+        query_pg_94 = """
         WITH upsert AS (
             UPDATE cache SET value = :value, ttl = sec2ttl(:ttl)
-             WHERE key=:key
+             WHERE key = :key
             RETURNING *)
         INSERT INTO cache (key, value, ttl)
         SELECT :key, :value, sec2ttl(:ttl)
-        WHERE NOT EXISTS (SELECT * FROM upsert)
+        WHERE NOT EXISTS (SELECT * FROM upsert);
         """
         value = json.dumps(value)
+        params = dict(key=self.prefix + key, value=value, ttl=ttl)
         with self.client.connect() as conn:
-            conn.execute(query, dict(key=self.prefix + key,
-                                     value=value, ttl=ttl))
+            server_version = conn.connection().dialect.server_version_info
+            if server_version >= (9, 5):
+                conn.execute(query, params)
+            else:
+                conn.execute(query_pg_94, params)
 
     def get(self, key):
         purge = "DELETE FROM cache WHERE ttl IS NOT NULL AND now() > ttl;"


Test patch:
diff --git a/tests/core/test_cache.py b/tests/core/test_cache.py
--- a/tests/core/test_cache.py
+++ b/tests/core/test_cache.py
@@ -97,14 +97,18 @@ def setUp(self):
 
     def test_retry_on_integrity_error(self):
         connect = mock.MagicMock()
-        connect.__enter__.return_value.execute.side_effect = [BackendError, None]
+        conn = connect.__enter__.return_value
+        conn.connection.return_value.dialect.server_version_info = (9, 4, 1)
+        conn.execute.side_effect = [BackendError, None]
         with mock.patch.object(self.cache.client, 'connect', return_value=connect):
             self.cache.set('this[]', 'value')
             assert connect.__enter__.return_value.execute.call_count == 2
 
     def test_retry_on_integrity_error_and_raises_after_a_few_tries(self):
         connect = mock.MagicMock()
-        connect.__enter__.return_value.execute.side_effect = BackendError
+        conn = connect.__enter__.return_value
+        conn.connection.return_value.dialect.server_version_info = (9, 5, 5)
+        conn.execute.side_effect = BackendError
         with mock.patch.object(self.cache.client, 'connect', return_value=connect):
             with self.assertRaises(BackendError):
                 self.cache.set('this[]', 'value')
Repository: Kinto/kinto

Issue:
If-Match:* behaviour is unclear (and crashes) with existing record
On 5.2 it would raise a "400" on existing record.

On current master, it will crash in "500" as it's not handled by `kinto.core.resource` anymore, but it's considered a valid header value by deserialization.

I think we should set the behavior to just ignore it. and process the request anyways. Opinions?
If-Match:* behaviour is unclear (and crashes) with existing record
On 5.2 it would raise a "400" on existing record.

On current master, it will crash in "500" as it's not handled by `kinto.core.resource` anymore, but it's considered a valid header value by deserialization.

I think we should set the behavior to just ignore it. and process the request anyways. Opinions?


Discussion:

Resolving patch:
diff --git a/kinto/core/resource/__init__.py b/kinto/core/resource/__init__.py
--- a/kinto/core/resource/__init__.py
+++ b/kinto/core/resource/__init__.py
@@ -838,7 +838,7 @@ def _raise_412_if_modified(self, record=None):
                 # Tombstones should not prevent creation.
                 return
             modified_since = -1  # Always raise.
-        elif if_match:
+        elif if_match and if_match != '*':
             modified_since = if_match
         else:
             # In case _raise_304_if_not_modified() did not raise.


Test patch:
diff --git a/tests/core/resource/test_preconditions.py b/tests/core/resource/test_preconditions.py
--- a/tests/core/resource/test_preconditions.py
+++ b/tests/core/resource/test_preconditions.py
@@ -196,6 +196,14 @@ def test_post_if_none_match_star_succeeds_if_record_does_not_exist(self):
                 'field': 'new'}}
         self.resource.collection_post()  # not raising.
 
+    def test_post_if_match_star_succeeds_if_record_does_exist(self):
+        self.validated['header']['If-Match'] = '*'
+        self.validated['body'] = {
+            'data': {
+                'id': self.resource.model.id_generator(),
+                'field': 'new'}}
+        self.resource.collection_post()  # not raising.
+
     def test_patch_returns_412_if_changed_meanwhile(self):
         self.resource.record_id = self.stored['id']
         self.assertRaises(httpexceptions.HTTPPreconditionFailed,
Repository: Kinto/kinto

Issue:
__flush__ produces an HTML reponse 
The response for the flush endpoint is a raw HTML response from Pyramid. I suggest make it a JSON response for compatibility with the rest of the API. Maybe return `{}`?

```
gsurita-30820:kinto gsurita$ http post http://localhost:8888/v1/__flush__
HTTP/1.1 202 Accepted
Content-Length: 157
Content-Type: text/html; charset=UTF-8
Date: Tue, 21 Feb 2017 13:56:56 GMT
Server: waitress
X-Content-Type-Options: nosniff

<html>
 <head>
  <title>202 Accepted</title>
 </head>
 <body>
  <h1>202 Accepted</h1>
  The request is accepted for processing.<br/><br/>



 </body>
</html>
__flush__ produces an HTML reponse 
The response for the flush endpoint is a raw HTML response from Pyramid. I suggest make it a JSON response for compatibility with the rest of the API. Maybe return `{}`?

```
gsurita-30820:kinto gsurita$ http post http://localhost:8888/v1/__flush__
HTTP/1.1 202 Accepted
Content-Length: 157
Content-Type: text/html; charset=UTF-8
Date: Tue, 21 Feb 2017 13:56:56 GMT
Server: waitress
X-Content-Type-Options: nosniff

<html>
 <head>
  <title>202 Accepted</title>
 </head>
 <body>
  <h1>202 Accepted</h1>
  The request is accepted for processing.<br/><br/>



 </body>
</html>


Discussion:
Yes I have seen this for a while and I quite agree.
Yes I have seen this for a while and I quite agree.

Resolving patch:
diff --git a/kinto/views/flush.py b/kinto/views/flush.py
--- a/kinto/views/flush.py
+++ b/kinto/views/flush.py
@@ -1,5 +1,4 @@
 from cornice import Service
-from pyramid import httpexceptions
 from pyramid.security import NO_PERMISSION_REQUIRED
 
 from kinto.events import ServerFlushed
@@ -16,4 +15,6 @@ def flush_post(request):
     request.registry.cache.flush()
     event = ServerFlushed(request)
     request.registry.notify(event)
-    return httpexceptions.HTTPAccepted()
+
+    request.response.status = 202
+    return {}


Test patch:
diff --git a/tests/test_views_flush.py b/tests/test_views_flush.py
--- a/tests/test_views_flush.py
+++ b/tests/test_views_flush.py
@@ -95,3 +95,7 @@ def test_can_be_enabled_via_environment(self):
         app = self.make_app(settings=extra)
         app.post('/__flush__', headers=self.headers)
         os.environ.pop('KINTO_FLUSH_ENDPOINT_ENABLED')
+
+    def test_flush_returns_json(self):
+        response = self.app.post('/__flush__', headers=self.headers, status=202)
+        self.assertEquals(response.json, {})
Repository: Kinto/kinto

Issue:
Crash with preconditions headers on permission endpoint
Custom model does not define collection timestamp method :)
Crash with preconditions headers on permission endpoint
Custom model does not define collection timestamp method :)


Discussion:

Resolving patch:
diff --git a/kinto/views/permissions.py b/kinto/views/permissions.py
--- a/kinto/views/permissions.py
+++ b/kinto/views/permissions.py
@@ -56,6 +56,9 @@ class PermissionsModel:
     def __init__(self, request):
         self.request = request
 
+    def timestamp(self, parent_id=None):
+        return 0
+
     def get_records(self, filters=None, sorting=None, pagination_rules=None,
                     limit=None, include_deleted=False, parent_id=None):
         # Invert the permissions inheritance tree.


Test patch:
diff --git a/tests/test_views_permissions.py b/tests/test_views_permissions.py
--- a/tests/test_views_permissions.py
+++ b/tests/test_views_permissions.py
@@ -95,6 +95,10 @@ def test_permissions_list_can_be_paginated(self):
         self.assertIn('Next-Page', resp.headers)
         self.assertEqual(len(resp.json['data']), 2)
 
+    def test_permissions_list_do_not_crash_with_preconditions(self):
+        headers = {'If-None-Match': '"123"', **self.headers}
+        self.app.get('/permissions', headers=headers)
+
 
 class GroupsPermissionTest(PermissionsViewTest):
Repository: Kinto/kinto

Issue:
Provide previous object in history deletion entries

Provide previous object in history deletion entries



Discussion:

Resolving patch:
diff --git a/kinto/__init__.py b/kinto/__init__.py
--- a/kinto/__init__.py
+++ b/kinto/__init__.py
@@ -13,7 +13,7 @@
 __version__ = pkg_resources.get_distribution(__package__).version
 
 # Implemented HTTP API Version
-HTTP_API_VERSION = '1.15'
+HTTP_API_VERSION = '1.16'
 
 # Main kinto logger
 logger = logging.getLogger(__name__)
diff --git a/kinto/plugins/history/listener.py b/kinto/plugins/history/listener.py
--- a/kinto/plugins/history/listener.py
+++ b/kinto/plugins/history/listener.py
@@ -11,6 +11,7 @@ def on_resource_changed(event):
     :mod:`kinto.plugins.history.views` module.
     """
     payload = event.payload
+    action = payload['action']
     resource_name = payload['resource_name']
     event_uri = payload['uri']
 
@@ -26,7 +27,7 @@ def on_resource_changed(event):
 
     targets = []
     for impacted in event.impacted_records:
-        target = impacted['new']
+        target = impacted['old'] if action == 'delete' else impacted['new']
         obj_id = target['id']
 
         try:


Test patch:
diff --git a/tests/plugins/test_history.py b/tests/plugins/test_history.py
--- a/tests/plugins/test_history.py
+++ b/tests/plugins/test_history.py
@@ -48,7 +48,8 @@ def setUp(self):
         self.app.put(self.bucket_uri, headers=self.headers)
 
         self.collection_uri = self.bucket_uri + '/collections/col'
-        resp = self.app.put(self.collection_uri, headers=self.headers)
+        body = {'data': {'signed': True}}
+        resp = self.app.put_json(self.collection_uri, body, headers=self.headers)
         self.collection = resp.json['data']
 
         self.group_uri = self.bucket_uri + '/groups/grp'
@@ -185,7 +186,7 @@ def test_tracks_collection_delete(self):
         resp = self.app.get(self.history_uri, headers=self.headers)
         entry = resp.json['data'][0]
         assert entry['action'] == 'delete'
-        assert entry['target']['data']['deleted'] is True
+        assert entry['target']['data']['signed']
 
     def test_tracks_multiple_collections_delete(self):
         self.app.put(self.bucket_uri + '/collections/col2',
@@ -235,7 +236,7 @@ def test_tracks_group_delete(self):
         resp = self.app.get(self.history_uri, headers=self.headers)
         entry = resp.json['data'][0]
         assert entry['action'] == 'delete'
-        assert entry['target']['data']['deleted'] is True
+        assert entry['target']['data']['members'] == ['elle']
 
     def test_tracks_multiple_groups_delete(self):
         self.app.put_json(self.bucket_uri + '/groups/g2',
@@ -289,7 +290,7 @@ def test_tracks_record_delete(self):
         resp = self.app.get(self.history_uri, headers=self.headers)
         entry = resp.json['data'][0]
         assert entry['action'] == 'delete'
-        assert entry['target']['data']['deleted'] is True
+        assert entry['target']['data']['foo'] == 42
 
     def test_tracks_multiple_records_delete(self):
         records_uri = self.collection_uri + '/records'
Repository: Kinto/kinto

Issue:
Enforce the permission endpoint when the admin plugin is included.

Enforce the permission endpoint when the admin plugin is included.



Discussion:
Could you please provide more information about that?
There is a setting to activate the permission endpoint: http://kinto.readthedocs.io/en/stable/configuration/settings.html#activating-the-permissions-endpoint

To activate the admin plugin we use: ``kinto.includes = kinto.plugins.admin``

What we want is to automatically activate the permission endpoint if the kinto_admin plugin is configured.
See https://github.com/Kinto/kinto/blob/master/kinto/__init__.py#L82-L84
Could you please provide more information about that?
There is a setting to activate the permission endpoint: http://kinto.readthedocs.io/en/stable/configuration/settings.html#activating-the-permissions-endpoint

To activate the admin plugin we use: ``kinto.includes = kinto.plugins.admin``

What we want is to automatically activate the permission endpoint if the kinto_admin plugin is configured.
See https://github.com/Kinto/kinto/blob/master/kinto/__init__.py#L82-L84

Resolving patch:
diff --git a/kinto/__init__.py b/kinto/__init__.py
--- a/kinto/__init__.py
+++ b/kinto/__init__.py
@@ -79,8 +79,9 @@ def main(global_config, config=None, **settings):
         kwargs['ignore'] = ['kinto.views.flush']
 
     # Permissions endpoint enabled if permission backend is setup.
+    is_admin_enabled = 'kinto.plugins.admin' in settings['includes']
     permissions_endpoint_enabled = (
-        asbool(settings['experimental_permissions_endpoint']) and
+        (is_admin_enabled or asbool(settings['experimental_permissions_endpoint'])) and
         hasattr(config.registry, 'permission'))
     if permissions_endpoint_enabled:
         config.add_api_capability(


Test patch:
diff --git a/tests/plugins/test_admin.py b/tests/plugins/test_admin.py
--- a/tests/plugins/test_admin.py
+++ b/tests/plugins/test_admin.py
@@ -50,6 +50,11 @@ def test_capability_is_exposed(self):
         }
         self.assertEqual(expected, capabilities['admin'])
 
+    def test_permission_endpoint_is_enabled_with_admin(self):
+        resp = self.app.get('/')
+        capabilities = resp.json['capabilities']
+        assert 'permissions_endpoint' in capabilities
+
     def test_admin_index_cat_be_reached(self):
         self.maxDiff = None
         resp = self.app.get('/admin/')
Repository: Kinto/kinto

Issue:
Storing bytes in the memory backend should convert it to str.
- This is an inconsistency with other backends that returns unicode when storing bytes.
Storing bytes in the memory backend should convert it to str.
- This is an inconsistency with other backends that returns unicode when storing bytes.


Discussion:

Resolving patch:
diff --git a/kinto/core/cache/memory.py b/kinto/core/cache/memory.py
--- a/kinto/core/cache/memory.py
+++ b/kinto/core/cache/memory.py
@@ -56,6 +56,8 @@ def expire(self, key, ttl):
 
     @synchronized
     def set(self, key, value, ttl=None):
+        if isinstance(value, bytes):
+            raise TypeError("a string-like object is required, not 'bytes'")
         self._clean_expired()
         self._clean_oversized()
         if ttl is not None:
diff --git a/kinto/core/cache/postgresql/__init__.py b/kinto/core/cache/postgresql/__init__.py
--- a/kinto/core/cache/postgresql/__init__.py
+++ b/kinto/core/cache/postgresql/__init__.py
@@ -121,6 +121,9 @@ def expire(self, key, ttl):
             conn.execute(query, dict(ttl=ttl, key=self.prefix + key))
 
     def set(self, key, value, ttl=None):
+        if isinstance(value, bytes):
+            raise TypeError("a string-like object is required, not 'bytes'")
+
         if ttl is None:
             logger.warning("No TTL for cache key '{}'".format(key))
         query = """


Test patch:
diff --git a/kinto/core/cache/testing.py b/kinto/core/cache/testing.py
--- a/kinto/core/cache/testing.py
+++ b/kinto/core/cache/testing.py
@@ -1,4 +1,5 @@
 import mock
+import pytest
 import time
 
 from pyramid import testing
@@ -100,6 +101,10 @@ def setget(k, v):
         self.assertEqual(*setget('foobar', {'b': [1, 2]}))
         self.assertEqual(*setget('foobar', 3.14))
 
+    def test_bytes_cannot_be_stored_in_the_cache(self):
+        with pytest.raises(TypeError):
+            self.cache.set('test', b'foo')
+
     def test_delete_removes_the_record(self):
         self.cache.set('foobar', 'toto')
         self.cache.delete('foobar')