Repository: googleapis/google-cloud-python

Issue:
Add 'user_project' support for bucket notification methods
#4127 (just merged) adds the new `storage.notification.BucketNotification` class.  Once #4056 (`requester_pays` buckets, `user_project` API requests) merges, we need to add `user_project` support to the new `BucketNotfication` API  wrapper methods.


Discussion:

Resolving patch:
diff --git a/storage/google/cloud/storage/notification.py b/storage/google/cloud/storage/notification.py
--- a/storage/google/cloud/storage/notification.py
+++ b/storage/google/cloud/storage/notification.py
@@ -233,6 +233,10 @@ def create(self, client=None):
 
         client = self._require_client(client)
 
+        query_params = {}
+        if self.bucket.user_project is not None:
+            query_params['userProject'] = self.bucket.user_project
+
         path = '/b/{}/notificationConfigs'.format(self.bucket.name)
         properties = self._properties.copy()
         properties['topic'] = _TOPIC_REF_FMT.format(
@@ -240,6 +244,7 @@ def create(self, client=None):
         self._properties = client._connection.api_request(
             method='POST',
             path=path,
+            query_params=query_params,
             data=properties,
         )
 
@@ -262,8 +267,17 @@ def exists(self, client=None):
             raise ValueError("Notification not intialized by server")
 
         client = self._require_client(client)
+
+        query_params = {}
+        if self.bucket.user_project is not None:
+            query_params['userProject'] = self.bucket.user_project
+
         try:
-            client._connection.api_request(method='GET', path=self.path)
+            client._connection.api_request(
+                method='GET',
+                path=self.path,
+                query_params=query_params,
+            )
         except NotFound:
             return False
         else:
@@ -288,7 +302,16 @@ def reload(self, client=None):
             raise ValueError("Notification not intialized by server")
 
         client = self._require_client(client)
-        response = client._connection.api_request(method='GET', path=self.path)
+
+        query_params = {}
+        if self.bucket.user_project is not None:
+            query_params['userProject'] = self.bucket.user_project
+
+        response = client._connection.api_request(
+            method='GET',
+            path=self.path,
+            query_params=query_params,
+        )
         self._set_properties(response)
 
     def delete(self, client=None):
@@ -310,4 +333,13 @@ def delete(self, client=None):
             raise ValueError("Notification not intialized by server")
 
         client = self._require_client(client)
-        client._connection.api_request(method='DELETE', path=self.path)
+
+        query_params = {}
+        if self.bucket.user_project is not None:
+            query_params['userProject'] = self.bucket.user_project
+
+        client._connection.api_request(
+            method='DELETE',
+            path=self.path,
+            query_params=query_params,
+        )


Test patch:
diff --git a/storage/tests/system.py b/storage/tests/system.py
--- a/storage/tests/system.py
+++ b/storage/tests/system.py
@@ -932,3 +932,23 @@ def test_notification_explicit(self):
                 notification.payload_format, self.payload_format())
         finally:
             notification.delete()
+
+    @unittest.skipUnless(USER_PROJECT, 'USER_PROJECT not set in environment.')
+    def test_notification_w_user_project(self):
+        new_bucket_name = 'notification-minimal' + unique_resource_id('-')
+        bucket = retry_429(Config.CLIENT.create_bucket)(
+            new_bucket_name, requester_pays=True)
+        self.case_buckets_to_delete.append(new_bucket_name)
+        with_user_project = Config.CLIENT.bucket(
+            new_bucket_name, user_project=USER_PROJECT)
+        self.assertEqual(list(with_user_project.list_notifications()), [])
+        notification = with_user_project.notification(self.TOPIC_NAME)
+        retry_429(notification.create)()
+        try:
+            self.assertTrue(notification.exists())
+            self.assertIsNotNone(notification.notification_id)
+            notifications = list(with_user_project.list_notifications())
+            self.assertEqual(len(notifications), 1)
+            self.assertEqual(notifications[0].topic_name, self.TOPIC_NAME)
+        finally:
+            notification.delete()
diff --git a/storage/tests/unit/test_notification.py b/storage/tests/unit/test_notification.py
--- a/storage/tests/unit/test_notification.py
+++ b/storage/tests/unit/test_notification.py
@@ -67,10 +67,11 @@ def _make_client(self, project=BUCKET_PROJECT):
 
         return mock.Mock(project=project, spec=Client)
 
-    def _make_bucket(self, client, name=BUCKET_NAME):
-         bucket = mock.Mock(spec=['client', 'name'])
+    def _make_bucket(self, client, name=BUCKET_NAME, user_project=None):
+         bucket = mock.Mock(spec=['client', 'name', 'user_project'])
          bucket.client= client
          bucket.name = name
+         bucket.user_project = user_project
          return bucket
 
     def test_ctor_defaults(self):
@@ -268,13 +269,15 @@ def test_create_w_defaults(self):
         api_request.assert_called_once_with(
             method='POST',
             path=self.CREATE_PATH,
+            query_params={},
             data=data,
         )
 
     def test_create_w_explicit_client(self):
+        USER_PROJECT = 'user-project-123'
         client = self._make_client()
         alt_client = self._make_client()
-        bucket = self._make_bucket(client)
+        bucket = self._make_bucket(client, user_project=USER_PROJECT)
         notification = self._make_one(
             bucket, self.TOPIC_NAME,
             topic_project=self.TOPIC_ALT_PROJECT,
@@ -317,6 +320,7 @@ def test_create_w_explicit_client(self):
         api_request.assert_called_once_with(
             method='POST',
             path=self.CREATE_PATH,
+            query_params={'userProject': USER_PROJECT},
             data=data,
         )
 
@@ -344,11 +348,13 @@ def test_exists_miss(self):
         api_request.assert_called_once_with(
             method='GET',
             path=self.NOTIFICATION_PATH,
+            query_params={},
         )
 
     def test_exists_hit(self):
+        USER_PROJECT = 'user-project-123'
         client = self._make_client()
-        bucket = self._make_bucket(client)
+        bucket = self._make_bucket(client, user_project=USER_PROJECT)
         alt_client = self._make_client()
         notification = self._make_one(bucket, self.TOPIC_NAME)
         notification._properties['id'] = self.NOTIFICATION_ID
@@ -365,6 +371,7 @@ def test_exists_hit(self):
         api_request.assert_called_once_with(
             method='GET',
             path=self.NOTIFICATION_PATH,
+            query_params={'userProject': USER_PROJECT},
         )
 
     def test_reload_wo_notification_id(self):
@@ -392,13 +399,15 @@ def test_reload_miss(self):
         api_request.assert_called_once_with(
             method='GET',
             path=self.NOTIFICATION_PATH,
+            query_params={},
         )
 
     def test_reload_hit(self):
         from google.cloud.storage.notification import NONE_PAYLOAD_FORMAT
 
+        USER_PROJECT = 'user-project-123'
         client = self._make_client()
-        bucket = self._make_bucket(client)
+        bucket = self._make_bucket(client, user_project=USER_PROJECT)
         alt_client = self._make_client()
         notification = self._make_one(bucket, self.TOPIC_NAME)
         notification._properties['id'] = self.NOTIFICATION_ID
@@ -423,6 +432,7 @@ def test_reload_hit(self):
         api_request.assert_called_once_with(
             method='GET',
             path=self.NOTIFICATION_PATH,
+            query_params={'userProject': USER_PROJECT},
         )
 
     def test_delete_wo_notification_id(self):
@@ -450,11 +460,13 @@ def test_delete_miss(self):
         api_request.assert_called_once_with(
             method='DELETE',
             path=self.NOTIFICATION_PATH,
+            query_params={},
         )
 
     def test_delete_hit(self):
+        USER_PROJECT = 'user-project-123'
         client = self._make_client()
-        bucket = self._make_bucket(client)
+        bucket = self._make_bucket(client, user_project=USER_PROJECT)
         alt_client = self._make_client()
         notification = self._make_one(bucket, self.TOPIC_NAME)
         notification._properties['id'] = self.NOTIFICATION_ID
@@ -466,4 +478,5 @@ def test_delete_hit(self):
         api_request.assert_called_once_with(
             method='DELETE',
             path=self.NOTIFICATION_PATH,
+            query_params={'userProject': USER_PROJECT},
         )