From 7190c86dd9f9aa7d13dfff752e0363dfe57a63aa Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 18 Sep 2017 14:22:15 -0400 Subject: [PATCH] Add 'BucketNotification.{reload,exists}' API wrappers. (#3986) Toward #3956. --- storage/google/cloud/storage/notification.py | 61 +++++++ storage/tests/unit/test_notification.py | 176 ++++++++++++++----- 2 files changed, 194 insertions(+), 43 deletions(-) diff --git a/storage/google/cloud/storage/notification.py b/storage/google/cloud/storage/notification.py index ce849e5acabf..4ca302a035be 100644 --- a/storage/google/cloud/storage/notification.py +++ b/storage/google/cloud/storage/notification.py @@ -14,6 +14,9 @@ """Support for bucket notification resources.""" +from google.api.core.exceptions import NotFound + + OBJECT_FINALIZE_EVENT_TYPE = 'OBJECT_FINALIZE' OBJECT_METADATA_UPDATE_EVENT_TYPE = 'OBJECT_METADATA_UPDATE' OBJECT_DELETE_EVENT_TYPE = 'OBJECT_DELETE' @@ -161,6 +164,15 @@ def _require_client(self, client): client = self.client return client + def _set_properties(self, response): + """Helper for :meth:`reload`. + + :type response: dict + :param response: resource mapping from server + """ + self._properties.clear() + self._properties.update(response) + def create(self, client=None): """API wrapper: create the notification. @@ -187,6 +199,54 @@ def create(self, client=None): data=properties, ) + def exists(self, client=None): + """Test whether this notification exists. + + See: + https://cloud.google.com/storage/docs/json_api/v1/notifications/get + + :type client: :class:`~google.cloud.storage.client.Client` or + ``NoneType`` + :param client: Optional. The client to use. If not passed, falls back + to the ``client`` stored on the current bucket. + + :rtype: bool + :returns: True, if the notification exists, else False. + :raises ValueError: if the notification has no ID. + """ + if self.notification_id is None: + raise ValueError("Notification not intialized by server") + + client = self._require_client(client) + try: + client._connection.api_request(method='GET', path=self.path) + except NotFound: + return False + else: + return True + + def reload(self, client=None): + """Update this notification from the server configuration. + + See: + https://cloud.google.com/storage/docs/json_api/v1/notifications/get + + :type client: :class:`~google.cloud.storage.client.Client` or + ``NoneType`` + :param client: Optional. The client to use. If not passed, falls back + to the ``client`` stored on the current bucket. + + :rtype: bool + :returns: True, if the notification exists, else False. + :raises ValueError: if the notification has no ID. + """ + if self.notification_id is None: + raise ValueError("Notification not intialized by server") + + client = self._require_client(client) + response = client._connection.api_request(method='GET', path=self.path) + self._set_properties(response) + def delete(self, client=None): """Delete this notification. @@ -200,6 +260,7 @@ def delete(self, client=None): :raises: :class:`google.api.core.exceptions.NotFound`: if the notification does not exist. + :raises ValueError: if the notification has no ID. """ if self.notification_id is None: raise ValueError("Notification not intialized by server") diff --git a/storage/tests/unit/test_notification.py b/storage/tests/unit/test_notification.py index d24c7a10a63d..af83c6e1533b 100644 --- a/storage/tests/unit/test_notification.py +++ b/storage/tests/unit/test_notification.py @@ -31,6 +31,12 @@ class TestBucketNotification(unittest.TestCase): 'attr2': 'value2', } BLOB_NAME_PREFIX = 'blob-name-prefix/' + NOTIFICATION_ID = '123' + SELF_LINK = 'https://example.com/notification/123' + ETAG = 'DEADBEEF' + CREATE_PATH = '/b/{}/notificationConfigs'.format(BUCKET_NAME) + NOTIFICATION_PATH = '/b/{}/notificationConfigs/{}'.format( + BUCKET_NAME, NOTIFICATION_ID) @staticmethod def event_types(): @@ -108,57 +114,50 @@ def test_ctor_explicit(self): def test_notification_id(self): client = self._make_client() bucket = self._make_bucket(client) - NOTIFICATION_ID = '123' notification = self._make_one( bucket, self.TOPIC_NAME) self.assertIsNone(notification.notification_id) - notification._properties['id'] = NOTIFICATION_ID - self.assertEqual(notification.notification_id, NOTIFICATION_ID) + notification._properties['id'] = self.NOTIFICATION_ID + self.assertEqual(notification.notification_id, self.NOTIFICATION_ID) def test_etag(self): client = self._make_client() bucket = self._make_bucket(client) - ETAG = 'DEADBEEF' notification = self._make_one( bucket, self.TOPIC_NAME) self.assertIsNone(notification.etag) - notification._properties['etag'] = ETAG - self.assertEqual(notification.etag, ETAG) + notification._properties['etag'] = self.ETAG + self.assertEqual(notification.etag, self.ETAG) def test_self_link(self): client = self._make_client() bucket = self._make_bucket(client) - SELF_LINK = 'https://example.com/notification/123' notification = self._make_one( bucket, self.TOPIC_NAME) self.assertIsNone(notification.self_link) - notification._properties['selfLink'] = SELF_LINK - self.assertEqual(notification.self_link, SELF_LINK) + notification._properties['selfLink'] = self.SELF_LINK + self.assertEqual(notification.self_link, self.SELF_LINK) def test_create_w_existing_notification_id(self): - NOTIFICATION_ID = '123' client = self._make_client() bucket = self._make_bucket(client) notification = self._make_one( bucket, self.TOPIC_NAME) - notification._properties['id'] = NOTIFICATION_ID + notification._properties['id'] = self.NOTIFICATION_ID with self.assertRaises(ValueError): notification.create() def test_create_w_defaults(self): - NOTIFICATION_ID = '123' - ETAG = 'DEADBEEF' - SELF_LINK = 'https://example.com/notification/123' client = self._make_client() bucket = self._make_bucket(client) notification = self._make_one( @@ -166,35 +165,31 @@ def test_create_w_defaults(self): api_request = client._connection.api_request api_request.return_value = { 'topic': self.TOPIC_REF, - 'id': NOTIFICATION_ID, - 'etag': ETAG, - 'selfLink': SELF_LINK, + 'id': self.NOTIFICATION_ID, + 'etag': self.ETAG, + 'selfLink': self.SELF_LINK, } notification.create() - self.assertEqual(notification.notification_id, NOTIFICATION_ID) - self.assertEqual(notification.etag, ETAG) - self.assertEqual(notification.self_link, SELF_LINK) + self.assertEqual(notification.notification_id, self.NOTIFICATION_ID) + self.assertEqual(notification.etag, self.ETAG) + self.assertEqual(notification.self_link, self.SELF_LINK) self.assertIsNone(notification.custom_attributes) self.assertIsNone(notification.event_types) self.assertIsNone(notification.blob_name_prefix) self.assertIsNone(notification.payload_format) - path = '/b/{}/notificationConfigs'.format(self.BUCKET_NAME) data = { 'topic': self.TOPIC_REF, } api_request.assert_called_once_with( method='POST', - path=path, + path=self.CREATE_PATH, data=data, ) def test_create_w_explicit_client(self): - NOTIFICATION_ID = '123' - ETAG = 'DEADBEEF' - SELF_LINK = 'https://example.com/notification/123' client = self._make_client() alt_client = self._make_client() bucket = self._make_bucket(client) @@ -213,9 +208,9 @@ def test_create_w_explicit_client(self): 'event_types': self.event_types(), 'blob_name_prefix': self.BLOB_NAME_PREFIX, 'payload_format': self.payload_format(), - 'id': NOTIFICATION_ID, - 'etag': ETAG, - 'selfLink': SELF_LINK, + 'id': self.NOTIFICATION_ID, + 'etag': self.ETAG, + 'selfLink': self.SELF_LINK, } notification.create(client=alt_client) @@ -226,11 +221,10 @@ def test_create_w_explicit_client(self): self.assertEqual(notification.blob_name_prefix, self.BLOB_NAME_PREFIX) self.assertEqual( notification.payload_format, self.payload_format()) - self.assertEqual(notification.notification_id, NOTIFICATION_ID) - self.assertEqual(notification.etag, ETAG) - self.assertEqual(notification.self_link, SELF_LINK) + self.assertEqual(notification.notification_id, self.NOTIFICATION_ID) + self.assertEqual(notification.etag, self.ETAG) + self.assertEqual(notification.self_link, self.SELF_LINK) - path = '/b/{}/notificationConfigs'.format(self.BUCKET_NAME) data = { 'topic': self.TOPIC_ALT_REF, 'custom_attributes': self.CUSTOM_ATTRIBUTES, @@ -240,10 +234,112 @@ def test_create_w_explicit_client(self): } api_request.assert_called_once_with( method='POST', - path=path, + path=self.CREATE_PATH, data=data, ) + def test_exists_wo_notification_id(self): + client = self._make_client() + bucket = self._make_bucket(client) + notification = self._make_one( + bucket, self.TOPIC_NAME) + + with self.assertRaises(ValueError): + notification.exists() + + def test_exists_miss(self): + from google.cloud.exceptions import NotFound + + client = self._make_client() + bucket = self._make_bucket(client) + notification = self._make_one(bucket, self.TOPIC_NAME) + notification._properties['id'] = self.NOTIFICATION_ID + api_request = client._connection.api_request + api_request.side_effect = NotFound('testing') + + self.assertFalse(notification.exists()) + + api_request.assert_called_once_with( + method='GET', + path=self.NOTIFICATION_PATH, + ) + + def test_exists_hit(self): + client = self._make_client() + bucket = self._make_bucket(client) + alt_client = self._make_client() + notification = self._make_one(bucket, self.TOPIC_NAME) + notification._properties['id'] = self.NOTIFICATION_ID + api_request = client._connection.api_request + api_request.return_value = { + 'topic': self.TOPIC_REF, + 'id': self.NOTIFICATION_ID, + 'etag': self.ETAG, + 'selfLink': self.SELF_LINK, + } + + self.assertTrue(notification.exists(client=client)) + + api_request.assert_called_once_with( + method='GET', + path=self.NOTIFICATION_PATH, + ) + + def test_reload_wo_notification_id(self): + client = self._make_client() + bucket = self._make_bucket(client) + notification = self._make_one( + bucket, self.TOPIC_NAME) + + with self.assertRaises(ValueError): + notification.reload() + + def test_reload_miss(self): + from google.cloud.exceptions import NotFound + + client = self._make_client() + bucket = self._make_bucket(client) + notification = self._make_one(bucket, self.TOPIC_NAME) + notification._properties['id'] = self.NOTIFICATION_ID + api_request = client._connection.api_request + api_request.side_effect = NotFound('testing') + + with self.assertRaises(NotFound): + notification.reload() + + api_request.assert_called_once_with( + method='GET', + path=self.NOTIFICATION_PATH, + ) + + def test_reload_hit(self): + client = self._make_client() + bucket = self._make_bucket(client) + alt_client = self._make_client() + notification = self._make_one(bucket, self.TOPIC_NAME) + notification._properties['id'] = self.NOTIFICATION_ID + api_request = client._connection.api_request + api_request.return_value = { + 'topic': self.TOPIC_REF, + 'id': self.NOTIFICATION_ID, + 'etag': self.ETAG, + 'selfLink': self.SELF_LINK, + } + + notification.reload(client=client) + + self.assertEqual(notification.etag, self.ETAG) + self.assertEqual(notification.self_link, self.SELF_LINK) + self.assertIsNone(notification.custom_attributes) + self.assertIsNone(notification.event_types) + self.assertIsNone(notification.blob_name_prefix) + self.assertIsNone(notification.payload_format) + + api_request.assert_called_once_with( + method='GET', + path=self.NOTIFICATION_PATH, + ) + def test_delete_wo_notification_id(self): client = self._make_client() bucket = self._make_bucket(client) @@ -256,39 +352,33 @@ def test_delete_wo_notification_id(self): def test_delete_miss(self): from google.cloud.exceptions import NotFound - NOTIFICATION_ID = '123' client = self._make_client() bucket = self._make_bucket(client) notification = self._make_one(bucket, self.TOPIC_NAME) - notification._properties['id'] = NOTIFICATION_ID + notification._properties['id'] = self.NOTIFICATION_ID api_request = client._connection.api_request api_request.side_effect = NotFound('testing') with self.assertRaises(NotFound): notification.delete() - path = '/b/{}/notificationConfigs/{}'.format( - self.BUCKET_NAME, NOTIFICATION_ID) api_request.assert_called_once_with( method='DELETE', - path=path, + path=self.NOTIFICATION_PATH, ) def test_delete_hit(self): - NOTIFICATION_ID = '123' client = self._make_client() bucket = self._make_bucket(client) alt_client = self._make_client() notification = self._make_one(bucket, self.TOPIC_NAME) - notification._properties['id'] = NOTIFICATION_ID + notification._properties['id'] = self.NOTIFICATION_ID api_request = client._connection.api_request api_request.return_value = None notification.delete(client=client) - path = '/b/{}/notificationConfigs/{}'.format( - self.BUCKET_NAME, NOTIFICATION_ID) api_request.assert_called_once_with( method='DELETE', - path=path, + path=self.NOTIFICATION_PATH, )