Repository: googleapis/google-cloud-python

Issue:
Consider allowing storage.bucket.Bucket.delete_key to silently fail if user requests it
From TODO:

``` python
    # TODO: Should we accept a 'silent' param here to not raise an exception?
```

See [`storage.bucket.Bucket.delete_key`](https://github.com/GoogleCloudPlatform/gcloud-python/blob/1f2aaf3606802f4f02eeb30e22e1082ef7f4594e/gcloud/storage/bucket.py#L158) for relevant code and #146 for some context.

`git blame` output

```
b7f1b774 (JJ Geewax   2014-01-29 18:02:29 -0500 158)     # TODO: Should we accept a 'silent' param here to not raise an exception?
```

Consider allowing storage.bucket.Bucket.delete_key to silently fail if user requests it
From TODO:

``` python
    # TODO: Should we accept a 'silent' param here to not raise an exception?
```

See [`storage.bucket.Bucket.delete_key`](https://github.com/GoogleCloudPlatform/gcloud-python/blob/1f2aaf3606802f4f02eeb30e22e1082ef7f4594e/gcloud/storage/bucket.py#L158) for relevant code and #146 for some context.

`git blame` output

```
b7f1b774 (JJ Geewax   2014-01-29 18:02:29 -0500 158)     # TODO: Should we accept a 'silent' param here to not raise an exception?
```



Discussion:
-1:  users who want to silence the exception can use 'try: ... except Blah: pass'

I tend to agree with you but will play devil's advocate anyhow.

I think JJ was targeting the case that `DELETE` is sent for an object that doesn't exist. I'd imagine in parallel code this could cause an issue if two deletes are sent.

From an App Engine remote API session using the datastore:

``` python
>>> from google.appengine.ext import ndb
>>> k = ndb.Key('A', 'b')
>>> print k
Key('A', 'b')
>>> print k.get()
None
>>> print k.delete()
None
```

The [API spec](https://cloud.google.com/storage/docs/json_api/v1/objects/delete) doesn't state it, but I assume that it returns a 404 in the case that the key maps to a non-existent object.  I still think propagating the `storage.exceptions.NotFoundError` raised by `storage.connection.Connection.api_request` is the more Pythonic thing to do.

If #312 lands, it could be the resolution for this issue as well:  users who want to delete without raising could use `bucket.delete_keys([key])` instead of `bucket.delete_key(key)`, and potentially check the result.

@silvolu I would recommend closing here:  users who don't care about the `NotFoundError` can call `bucket.delete_keys([key])` omitting the `on_error` callback added in #312.

Sounds good, should we add something to the docs for `delete_keys` to make it clear, e.g.: "You can use  this method with a single key if you want to suppress any `NotFoundError`, by omitting the `on_error` callback" ?  

Actually, it would need to be different, since omitting the callback (now) causes the error to be raised.  To suppress the error, you would need to call `bucket.delete_keys([key], on_error=lambda key: pass)`.

Oh, ok. Should we add that to the doc? Might be not immediately clear.

-1:  users who want to silence the exception can use 'try: ... except Blah: pass'

I tend to agree with you but will play devil's advocate anyhow.

I think JJ was targeting the case that `DELETE` is sent for an object that doesn't exist. I'd imagine in parallel code this could cause an issue if two deletes are sent.

From an App Engine remote API session using the datastore:

``` python
>>> from google.appengine.ext import ndb
>>> k = ndb.Key('A', 'b')
>>> print k
Key('A', 'b')
>>> print k.get()
None
>>> print k.delete()
None
```

The [API spec](https://cloud.google.com/storage/docs/json_api/v1/objects/delete) doesn't state it, but I assume that it returns a 404 in the case that the key maps to a non-existent object.  I still think propagating the `storage.exceptions.NotFoundError` raised by `storage.connection.Connection.api_request` is the more Pythonic thing to do.

If #312 lands, it could be the resolution for this issue as well:  users who want to delete without raising could use `bucket.delete_keys([key])` instead of `bucket.delete_key(key)`, and potentially check the result.

@silvolu I would recommend closing here:  users who don't care about the `NotFoundError` can call `bucket.delete_keys([key])` omitting the `on_error` callback added in #312.

Sounds good, should we add something to the docs for `delete_keys` to make it clear, e.g.: "You can use  this method with a single key if you want to suppress any `NotFoundError`, by omitting the `on_error` callback" ?  

Actually, it would need to be different, since omitting the callback (now) causes the error to be raised.  To suppress the error, you would need to call `bucket.delete_keys([key], on_error=lambda key: pass)`.

Oh, ok. Should we add that to the doc? Might be not immediately clear.


Resolving patch:
diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py
--- a/gcloud/storage/bucket.py
+++ b/gcloud/storage/bucket.py
@@ -189,7 +189,11 @@ def delete_key(self, key):
 
         :rtype: :class:`gcloud.storage.key.Key`
         :returns: The key that was just deleted.
-        :raises: :class:`gcloud.storage.exceptions.NotFoundError`
+        :raises: :class:`gcloud.storage.exceptions.NotFoundError` (to suppress
+                 the exception, call ``delete_keys``, passing a no-op
+                 ``on_error`` callback, e.g.::
+
+                 >>> bucket.delete_keys([key], on_error=lambda key: pass)
         """
         key = self.new_key(key)
         self.connection.api_request(method='DELETE', path=key.path)
@@ -204,8 +208,12 @@ def delete_keys(self, keys, on_error=None):
         :param keys: A list of key names or Key objects to delete.
 
         :type on_error: a callable taking (key)
-        :param on_error: If not ``None``, called once for each key which
-                         raises a ``NotFoundError``.
+        :param on_error: If not ``None``, called once for each key raising
+                         :class:`gcloud.storage.exceptions.NotFoundError`;
+                         otherwise, the exception is propagated.
+
+        :raises: :class:`gcloud.storage.exceptions.NotFoundError` (if
+                 `on_error` is not passed).
         """
         for key in keys:
             try:
diff --git a/gcloud/storage/key.py b/gcloud/storage/key.py
--- a/gcloud/storage/key.py
+++ b/gcloud/storage/key.py
@@ -165,6 +165,9 @@ def delete(self):
 
         :rtype: :class:`Key`
         :returns: The key that was just deleted.
+        :raises: :class:`gcloud.storage.exceptions.NotFoundError`
+                 (propagated from
+                 :meth:`gcloud.storage.bucket.Bucket.delete_key`).
         """
         return self.bucket.delete_key(self)
 


Test patch: