Repository: minio/minio-py

Issue:
ListenBucketNotification should also support Get and Head
Depends on minio/minio#3916

Discussion:

Patch:
diff --git a/docs/API.md b/docs/API.md
index 993c435..2e77e33 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -240,7 +240,6 @@ __Return Value__
 |``object.last_modified`` |_datetime.datetime_ | modified time stamp.  |
 
 
-
 __Example__
 
 ```py
@@ -544,14 +543,15 @@ __Parameters__
 |``suffix`` | _string_  | Object key suffix to filter notifications for. |
 |``events`` | _list_ | Enables notifications for specific event types. |
 
-See [here](../examples/listen_notification.py) for a full example.
+See [here](https://raw.githubusercontent.com/minio/minio-py/master/examples/listen_notification.py) for a full example.
 
 ```py
 # Put a file with default content-type.
 events = minioClient.listen_bucket_notification('my-bucket', 'my-prefix/',
                                                 '.my-suffix',
                                                 ['s3:ObjectCreated:*',
-                                                 's3:ObjectRemoved:*'])
+                                                 's3:ObjectRemoved:*',
+                                                 's3:ObjectAccessed:*'])
 for event in events:
     print event
 ```
diff --git a/examples/listen_notification.py b/examples/listen_notification.py
index d4855e3..0eb309f 100644
--- a/examples/listen_notification.py
+++ b/examples/listen_notification.py
@@ -27,6 +27,7 @@ client = Minio('play.minio.io:9000',
 events = client.listen_bucket_notification('my-bucket', 'my-prefix/',
                                            '.my-suffix',
                                            ['s3:ObjectCreated:*',
-                                            's3:ObjectRemoved:*'])
+                                            's3:ObjectRemoved:*',
+                                            's3:ObjectAccessed:*'])
 for event in events:
     print(event)
diff --git a/minio/api.py b/minio/api.py
index 13198af..4646e78 100644
--- a/minio/api.py
+++ b/minio/api.py
@@ -485,7 +485,8 @@ class Minio(object):
 
     def listen_bucket_notification(self, bucket_name, prefix='', suffix='',
                                    events=['s3:ObjectCreated:*',
-                                           's3:ObjectRemoved:*']):
+                                           's3:ObjectRemoved:*',
+                                           's3:ObjectAccessed:*']):
         """
         Yeilds new event notifications on a bucket, caller should iterate
         to read new notifications.
@@ -504,7 +505,7 @@ class Minio(object):
         url_components = urlsplit(self._endpoint_url)
         if url_components.hostname == 's3.amazonaws.com':
             raise InvalidArgumentError(
-                'Listening for event notifications on a  bucket is a Minio '
+                'Listening for event notifications on a bucket is a Minio '
                 'specific extension to bucket notification API. It is not '
                 'supported by Amazon S3')
 
@@ -1082,7 +1083,7 @@ class Minio(object):
             'Content-Md5': get_md5_base64digest(content),
             'Content-Length': len(content)
         }
-        query = {"delete": None}
+        query = {'delete': ''}
         content_sha256_hex = get_sha256_hexdigest(content)
 
         # send multi-object delete request
@@ -1778,7 +1779,7 @@ class Minio(object):
         :return: location of bucket name is returned.
         """
         method = 'GET'
-        url = self._endpoint_url + '/' + bucket_name + '?location'
+        url = self._endpoint_url + '/' + bucket_name + '?location='
         headers = {}
         # default for all requests.
         region = 'us-east-1'
diff --git a/minio/signer.py b/minio/signer.py
index 5cebf5e..b514cb3 100644
--- a/minio/signer.py
+++ b/minio/signer.py
@@ -207,6 +207,7 @@ def sign_v4(method, url, region, headers=None, access_key=None,
                                                headers_to_sign,
                                                signed_headers,
                                                content_sha256)
+
     string_to_sign = generate_string_to_sign(date, region,
                                              canonical_req)
     signing_key = generate_signing_key(date, region, secret_key)
@@ -232,16 +233,7 @@ def generate_canonical_request(method, parsed_url, headers, signed_headers, cont
     :param headers: HTTP header dictionary.
     :param content_sha256: Content sha256 hexdigest string.
     """
-    lines = [method, parsed_url.path]
-
-    # Parsed query.
-    split_query = parsed_url.query.split('&')
-    split_query.sort()
-    for i in range(0, len(split_query)):
-        if len(split_query[i]) > 0 and '=' not in split_query[i]:
-            split_query[i] += '='
-    query = '&'.join(split_query)
-    lines.append(query)
+    lines = [method, parsed_url.path, parsed_url.query]
 
     # Headers added to canonical request.
     header_lines = []


Test patch:
diff --git a/tests/unit/remove_objects_test.py b/tests/unit/remove_objects_test.py
index bfbf592..1142523 100644
--- a/tests/unit/remove_objects_test.py
+++ b/tests/unit/remove_objects_test.py
@@ -56,7 +56,7 @@ class RemoveObjectsTest(TestCase):
         mock_connection.return_value = mock_server
         mock_server.mock_add_request(
             MockResponse('POST',
-                         'https://localhost:9000/hello/?delete',
+                         'https://localhost:9000/hello/?delete=',
                          {'Content-Length': 95,
                           'User-Agent': _DEFAULT_USER_AGENT,
                           'Content-Md5': u'5Tg5SmU9Or43L4+iIyfPrQ=='}, 200,
@@ -72,7 +72,7 @@ class RemoveObjectsTest(TestCase):
         mock_connection.return_value = mock_server
         mock_server.mock_add_request(
             MockResponse('POST',
-                         'https://localhost:9000/hello/?delete',
+                         'https://localhost:9000/hello/?delete=',
                          {'Content-Length': 95,
                           'User-Agent': _DEFAULT_USER_AGENT,
                           'Content-Md5': u'5Tg5SmU9Or43L4+iIyfPrQ=='}, 200,
@@ -88,7 +88,7 @@ class RemoveObjectsTest(TestCase):
         mock_connection.return_value = mock_server
         mock_server.mock_add_request(
             MockResponse('POST',
-                         'https://localhost:9000/hello/?delete',
+                         'https://localhost:9000/hello/?delete=',
                          {'Content-Length': 95,
                           'User-Agent': _DEFAULT_USER_AGENT,
                           'Content-Md5': u'5Tg5SmU9Or43L4+iIyfPrQ=='}, 200,
diff --git a/tests/unit/sign_test.py b/tests/unit/sign_test.py
index 3ccbb84..1f71238 100644
--- a/tests/unit/sign_test.py
+++ b/tests/unit/sign_test.py
@@ -54,7 +54,7 @@ class CanonicalRequestTest(TestCase):
     def test_request_with_query(self):
         url = urlsplit('http://localhost:9000/hello?c=d&e=f&a=b')
         expected_signed_headers = ['x-amz-content-sha256', 'x-amz-date']
-        expected_request_array = ['PUT', '/hello', 'a=b&c=d&e=f',
+        expected_request_array = ['PUT', '/hello', 'c=d&e=f&a=b',
                                   'x-amz-content-sha256:' + empty_hash,
                                   'x-amz-date:dateString',
                                   '', ';'.join(expected_signed_headers),