Repository: Kinto/kinto-http.py

Issue:
Add support of history plugin
See https://github.com/Kinto/kinto-http.js/#listing-bucket-history

* By bucket
* Filtrable
* Sortable

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a07b4bc..e76fadc 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -7,7 +7,9 @@ This document describes changes between each past release.
 10.5.0 (unreleased)
 ===================
 
-- Nothing changed yet.
+**New features**
+
+- Add history support (fixes #112), Thanks @FlorianKuckelkorn!
 
 
 10.4.1 (2019-05-22)
diff --git a/README.rst b/README.rst
index 2d36be3..f24b0d7 100644
--- a/README.rst
+++ b/README.rst
@@ -317,6 +317,28 @@ of them, you can specify the number of pages:
     records = client.get_records(_limit=10, pages=float('inf'))  # Infinity
 
 
+History
+-------
+
+If the built-in `history plugin <https://kinto.readthedocs.io/en/latest/api/1.x/history.html>`_ is enabled, it is possible to retrieve the history of changes:
+
+.. code-block:: python
+
+    # Get the complete history of a bucket
+    changes = client.get_history(bucket='default')
+
+    # and optionally use filters
+    hist = client.get_history(bucket='default', _limit=2, _sort='-last_modified', _since='1533762576015')
+    hist = client.get_history(bucket='default', resource_name='collection')
+
+
+The history of a bucket can also be purged with:
+
+.. code-block:: python
+
+    client.purge_history(bucket='default')
+
+
 Endpoint URLs
 -------------
 
diff --git a/kinto_http/__init__.py b/kinto_http/__init__.py
index 6955881..87dc701 100644
--- a/kinto_http/__init__.py
+++ b/kinto_http/__init__.py
@@ -47,6 +47,7 @@ class Endpoints(object):
         "batch": "{root}/batch",
         "buckets": "{root}/buckets",
         "bucket": "{root}/buckets/{bucket}",
+        "history": "{root}/buckets/{bucket}/history",
         "groups": "{root}/buckets/{bucket}/groups",
         "group": "{root}/buckets/{bucket}/groups/{group}",
         "collections": "{root}/buckets/{bucket}/collections",
@@ -862,6 +863,18 @@ class Client(object):
         resp, _ = self.session.request("delete", endpoint, headers=headers)
         return resp["data"]
 
+    def get_history(self, *, bucket=None, **kwargs):
+        endpoint = self.get_endpoint("history", bucket=bucket)
+        logger.info("Get history from bucket %r" % bucket or self._bucket_name)
+        return self._paginated(endpoint, **kwargs)
+
+    def purge_history(self, *, bucket=None, safe=True, if_match=None):
+        endpoint = self.get_endpoint('history', bucket=bucket)
+        headers = self._get_cache_headers(safe, if_match=if_match)
+        logger.info("Purge History of bucket %r" % bucket or self._bucket_name)
+        resp, _ = self.session.request('delete', endpoint, headers=headers)
+        return resp['data']
+
     def __repr__(self):
         if self._collection_name:
             endpoint = self.get_endpoint(


Test patch:
diff --git a/kinto_http/tests/functional.py b/kinto_http/tests/functional.py
index 5a7f70e..bd0f241 100644
--- a/kinto_http/tests/functional.py
+++ b/kinto_http/tests/functional.py
@@ -142,7 +142,7 @@ class FunctionalTest(unittest.TestCase):
     def test_group_creation_if_bucket_does_not_exist(self):
         with pytest.raises(KintoException) as e:
             self.client.create_group(id="payments", bucket="mozilla", data={"members": ["blah"]})
-        assert str(e).endswith(
+        assert str(e.value).endswith(
             "PUT /v1/buckets/mozilla/groups/payments - "
             "403 Unauthorized. Please check that the "
             "bucket exists and that you have the permission "
diff --git a/kinto_http/tests/test_client.py b/kinto_http/tests/test_client.py
index 0d39af9..59ccf6a 100644
--- a/kinto_http/tests/test_client.py
+++ b/kinto_http/tests/test_client.py
@@ -1182,3 +1182,43 @@ class RecordTest(unittest.TestCase):
             permissions=None,
             headers=None,
         )
+
+
+class HistoryTest(unittest.TestCase):
+    def setUp(self):
+        self.session = mock.MagicMock()
+        self.client = Client(session=self.session)
+
+    def test_basic_retrivial_of_bucket_history(self):
+        mock_response(self.session)
+        self.client.get_history(bucket='mybucket')
+        url = '/buckets/mybucket/history'
+        self.session.request.assert_called_with('get', url, headers={}, params={})
+
+    def test_filter_sorting_operations_on_bucket_history(self):
+        mock_response(self.session)
+        self.client.get_history(bucket='mybucket',
+                                _limit=2,
+                                _sort='-last_modified',
+                                _since='1533762576015')
+
+        url = '/buckets/mybucket/history'
+        self.session.request.assert_called_with('get', url, headers={},
+                                                params={'_limit': 2,
+                                                        '_sort': '-last_modified',
+                                                        '_since': '1533762576015'}
+                                                )
+
+    def test_filtering_by_resource_name(self):
+        mock_response(self.session)
+        self.client.get_history(bucket='mybucket', resource_name='collection')
+        url = '/buckets/mybucket/history'
+        self.session.request.assert_called_with('get', url, headers={},
+                                                params={'resource_name': 'collection'}
+                                                )
+
+    def test_purging_of_history(self):
+        mock_response(self.session)
+        self.client.purge_history(bucket='mybucket')
+        url = '/buckets/mybucket/history'
+        self.session.request.assert_called_with('delete', url, headers=None)
diff --git a/kinto_http/tests/test_endpoints.py b/kinto_http/tests/test_endpoints.py
index 33d7898..52f27d2 100644
--- a/kinto_http/tests/test_endpoints.py
+++ b/kinto_http/tests/test_endpoints.py
@@ -38,6 +38,9 @@ class EndpointsTest(unittest.TestCase):
             == "/buckets/buck/collections/coll/records/1"
         )
 
+    def test_history(self):
+        assert self.endpoints.get("history", **self.kwargs) == "/buckets/buck/history"
+
     def test_missing_arguments_raise_an_error(self):
         # Don't include the record id; it should raise an error.
         with self.assertRaises(KintoException) as context: