Skip to content

Commit 49b6e7d

Browse files
authored
Add 'BucketNotification.create' API wrapper. (#3966)
Toward #3956.
1 parent 327347e commit 49b6e7d

2 files changed

Lines changed: 183 additions & 22 deletions

File tree

storage/google/cloud/storage/notification.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
JSON_API_V1_PAYLOAD_FORMAT = 'JSON_API_V1'
2323
NONE_PAYLOAD_FORMAT = 'NONE'
2424

25+
_TOPIC_REF = '//pubsub.googleapis.com/projects/{}/topics/{}'
26+
2527

2628
class BucketNotification(object):
2729
"""Represent a single notification resource for a bucket.
@@ -133,3 +135,48 @@ def etag(self):
133135
def self_link(self):
134136
"""Server-set ETag of notification resource."""
135137
return self._properties.get('selfLink')
138+
139+
@property
140+
def client(self):
141+
"""The client bound to this notfication."""
142+
return self.bucket.client
143+
144+
def _require_client(self, client):
145+
"""Check client or verify over-ride.
146+
147+
:type client: :class:`~google.cloud.storage.client.Client` or
148+
``NoneType``
149+
:param client: the client to use.
150+
151+
:rtype: :class:`google.cloud.storage.client.Client`
152+
:returns: The client passed in or the bucket's client.
153+
"""
154+
if client is None:
155+
client = self.client
156+
return client
157+
158+
def create(self, client=None):
159+
"""API wrapper: create the notification.
160+
161+
See:
162+
https://cloud.google.com/storage/docs/json_api/v1/notifications/insert
163+
164+
:type client: :class:`~google.cloud.storage.client.Client`
165+
:param client: (Optional) the client to use. If not passed, falls back
166+
to the ``client`` stored on the notification's bucket.
167+
"""
168+
if self.notification_id is not None:
169+
raise ValueError("Notification already exists w/ id: {}".format(
170+
self.notification_id))
171+
172+
client = self._require_client(client)
173+
174+
path = '/b/{}/notificationConfigs'.format(self.bucket.name)
175+
properties = self._properties.copy()
176+
properties['topic'] = _TOPIC_REF.format(
177+
self.topic_project, self.topic_name)
178+
self._properties = client._connection.api_request(
179+
method='POST',
180+
path=path,
181+
data=properties,
182+
)

storage/tests/unit/test_notification.py

Lines changed: 136 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,29 @@ class TestBucketNotification(unittest.TestCase):
2323
BUCKET_PROJECT = 'bucket-project-123'
2424
TOPIC_NAME = 'test-topic'
2525
TOPIC_ALT_PROJECT = 'topic-project-456'
26+
TOPIC_REF_FMT = '//pubsub.googleapis.com/projects/{}/topics/{}'
27+
TOPIC_REF = TOPIC_REF_FMT.format(BUCKET_PROJECT, TOPIC_NAME)
28+
TOPIC_ALT_REF = TOPIC_REF_FMT.format(TOPIC_ALT_PROJECT, TOPIC_NAME)
29+
CUSTOM_ATTRIBUTES = {
30+
'attr1': 'value1',
31+
'attr2': 'value2',
32+
}
33+
BLOB_NAME_PREFIX = 'blob-name-prefix/'
34+
35+
@staticmethod
36+
def event_types():
37+
from google.cloud.storage.notification import (
38+
OBJECT_FINALIZE_EVENT_TYPE,
39+
OBJECT_DELETE_EVENT_TYPE)
40+
41+
return [OBJECT_FINALIZE_EVENT_TYPE, OBJECT_DELETE_EVENT_TYPE]
42+
43+
@staticmethod
44+
def payload_format():
45+
from google.cloud.storage.notification import (
46+
JSON_API_V1_PAYLOAD_FORMAT)
47+
48+
return JSON_API_V1_PAYLOAD_FORMAT
2649

2750
@staticmethod
2851
def _get_target_class():
@@ -39,9 +62,10 @@ def _make_client(self, project=BUCKET_PROJECT):
3962
return mock.Mock(project=project, spec=Client)
4063

4164
def _make_bucket(self, client, name=BUCKET_NAME):
42-
from google.cloud.storage.bucket import Bucket
43-
44-
return mock.Mock(client=client, name=name, spec=Bucket)
65+
bucket = mock.Mock(spec=['client', 'name'])
66+
bucket.client= client
67+
bucket.name = name
68+
return bucket
4569

4670
def test_ctor_defaults(self):
4771
client = self._make_client()
@@ -59,37 +83,27 @@ def test_ctor_defaults(self):
5983
self.assertIsNone(notification.payload_format)
6084

6185
def test_ctor_explicit(self):
62-
from google.cloud.storage.notification import (
63-
OBJECT_FINALIZE_EVENT_TYPE,
64-
OBJECT_DELETE_EVENT_TYPE,
65-
JSON_API_V1_PAYLOAD_FORMAT)
66-
6786
client = self._make_client()
6887
bucket = self._make_bucket(client)
69-
CUSTOM_ATTRIBUTES = {
70-
'attr1': 'value1',
71-
'attr2': 'value2',
72-
}
73-
EVENT_TYPES = [OBJECT_FINALIZE_EVENT_TYPE, OBJECT_DELETE_EVENT_TYPE]
74-
BLOB_NAME_PREFIX = 'blob-name-prefix/'
7588

7689
notification = self._make_one(
7790
bucket, self.TOPIC_NAME,
7891
topic_project=self.TOPIC_ALT_PROJECT,
79-
custom_attributes=CUSTOM_ATTRIBUTES,
80-
event_types=EVENT_TYPES,
81-
blob_name_prefix=BLOB_NAME_PREFIX,
82-
payload_format=JSON_API_V1_PAYLOAD_FORMAT,
92+
custom_attributes=self.CUSTOM_ATTRIBUTES,
93+
event_types=self.event_types(),
94+
blob_name_prefix=self.BLOB_NAME_PREFIX,
95+
payload_format=self.payload_format(),
8396
)
8497

8598
self.assertIs(notification.bucket, bucket)
8699
self.assertEqual(notification.topic_name, self.TOPIC_NAME)
87100
self.assertEqual(notification.topic_project, self.TOPIC_ALT_PROJECT)
88-
self.assertEqual(notification.custom_attributes, CUSTOM_ATTRIBUTES)
89-
self.assertEqual(notification.event_types, EVENT_TYPES)
90-
self.assertEqual(notification.blob_name_prefix, BLOB_NAME_PREFIX)
91101
self.assertEqual(
92-
notification.payload_format, JSON_API_V1_PAYLOAD_FORMAT)
102+
notification.custom_attributes, self.CUSTOM_ATTRIBUTES)
103+
self.assertEqual(notification.event_types, self.event_types())
104+
self.assertEqual(notification.blob_name_prefix, self.BLOB_NAME_PREFIX)
105+
self.assertEqual(
106+
notification.payload_format, self.payload_format())
93107

94108
def test_notification_id(self):
95109
client = self._make_client()
@@ -129,3 +143,103 @@ def test_self_link(self):
129143

130144
notification._properties['selfLink'] = SELF_LINK
131145
self.assertEqual(notification.self_link, SELF_LINK)
146+
147+
def test_create_w_existing_notification_id(self):
148+
NOTIFICATION_ID = '123'
149+
client = self._make_client()
150+
bucket = self._make_bucket(client)
151+
notification = self._make_one(
152+
bucket, self.TOPIC_NAME)
153+
notification._properties['id'] = NOTIFICATION_ID
154+
155+
with self.assertRaises(ValueError):
156+
notification.create()
157+
158+
def test_create_w_defaults(self):
159+
NOTIFICATION_ID = '123'
160+
ETAG = 'DEADBEEF'
161+
SELF_LINK = 'https://example.com/notification/123'
162+
client = self._make_client()
163+
bucket = self._make_bucket(client)
164+
notification = self._make_one(
165+
bucket, self.TOPIC_NAME)
166+
api_request = client._connection.api_request
167+
api_request.return_value = {
168+
'topic': self.TOPIC_REF,
169+
'id': NOTIFICATION_ID,
170+
'etag': ETAG,
171+
'selfLink': SELF_LINK,
172+
}
173+
174+
notification.create()
175+
176+
self.assertEqual(notification.notification_id, NOTIFICATION_ID)
177+
self.assertEqual(notification.etag, ETAG)
178+
self.assertEqual(notification.self_link, SELF_LINK)
179+
self.assertIsNone(notification.custom_attributes)
180+
self.assertIsNone(notification.event_types)
181+
self.assertIsNone(notification.blob_name_prefix)
182+
self.assertIsNone(notification.payload_format)
183+
184+
path = '/b/{}/notificationConfigs'.format(self.BUCKET_NAME)
185+
data = {
186+
'topic': self.TOPIC_REF,
187+
}
188+
api_request.assert_called_once_with(
189+
method='POST',
190+
path=path,
191+
data=data,
192+
)
193+
194+
def test_create_w_explicit_client(self):
195+
NOTIFICATION_ID = '123'
196+
ETAG = 'DEADBEEF'
197+
SELF_LINK = 'https://example.com/notification/123'
198+
client = self._make_client()
199+
alt_client = self._make_client()
200+
bucket = self._make_bucket(client)
201+
notification = self._make_one(
202+
bucket, self.TOPIC_NAME,
203+
topic_project=self.TOPIC_ALT_PROJECT,
204+
custom_attributes=self.CUSTOM_ATTRIBUTES,
205+
event_types=self.event_types(),
206+
blob_name_prefix=self.BLOB_NAME_PREFIX,
207+
payload_format=self.payload_format(),
208+
)
209+
api_request = alt_client._connection.api_request
210+
api_request.return_value = {
211+
'topic': self.TOPIC_ALT_REF,
212+
'custom_attributes': self.CUSTOM_ATTRIBUTES,
213+
'event_types': self.event_types(),
214+
'blob_name_prefix': self.BLOB_NAME_PREFIX,
215+
'payload_format': self.payload_format(),
216+
'id': NOTIFICATION_ID,
217+
'etag': ETAG,
218+
'selfLink': SELF_LINK,
219+
}
220+
221+
notification.create(client=alt_client)
222+
223+
self.assertEqual(
224+
notification.custom_attributes, self.CUSTOM_ATTRIBUTES)
225+
self.assertEqual(notification.event_types, self.event_types())
226+
self.assertEqual(notification.blob_name_prefix, self.BLOB_NAME_PREFIX)
227+
self.assertEqual(
228+
notification.payload_format, self.payload_format())
229+
self.assertEqual(notification.notification_id, NOTIFICATION_ID)
230+
self.assertEqual(notification.etag, ETAG)
231+
self.assertEqual(notification.self_link, SELF_LINK)
232+
233+
path = '/b/{}/notificationConfigs'.format(self.BUCKET_NAME)
234+
data = {
235+
'topic': self.TOPIC_ALT_REF,
236+
'custom_attributes': self.CUSTOM_ATTRIBUTES,
237+
'event_types': self.event_types(),
238+
'blob_name_prefix': self.BLOB_NAME_PREFIX,
239+
'payload_format': self.payload_format(),
240+
}
241+
api_request.assert_called_once_with(
242+
method='POST',
243+
path=path,
244+
data=data,
245+
)

0 commit comments

Comments
 (0)