Repository: googleapis/google-cloud-python

Issue:
Setting lifecycle rules totally undocumented
It took me quite a while to figure out how to set lifecycle rules. If you look at the code, it's undocumented:

```
@lifecycle_rules.setter
def lifecycle_rules(self, rules):
    self._patch_property('lifecycle', {'rule': rules})
```

I found [some tests for this](https://github.com/GoogleCloudPlatform/google-cloud-python/blob/090c5d583da82daabf05c52e8f72b858a0bb09c2/storage/unit_tests/test_bucket.py#L588-L596), but even those don't seem to use the `lifecycle_rules` method. Not having a documented method for doing this is very frustrating since this is supposed to be a library that supports all of the features. 



Discussion:
@mlissner, thanks for pointing this out.

This library is still under heavy development and hasn't reached 1.0 yet, which is why some areas are missing or light on documentation. There are also a number of features that aren't yet supported by this library.

@mlissner That was the setter, you want the [getter](https://github.com/GoogleCloudPlatform/google-cloud-python/blob/b6fe01ec4934d7744b5df8a7e64531f4c1386a9e/storage/google/cloud/storage/bucket.py#L563-L571). Is your concern that the [documentation](https://googlecloudplatform.github.io/google-cloud-python/0.20.0/storage-buckets.html#google.cloud.storage.bucket.Bucket.lifecycle_rules) for the property is insufficient / doesn't indicate that there **is** a setter?

In fact, the `@property` descriptor explicitly takes the `__doc__` from the getter:  any docstring on from the setter or deleter are not discoverable at runtime.

> Is your concern that the documentation for the property is insufficient / doesn't indicate that there is a setter?

Yes, exactly. I want to set the lifecycle rules for a bucket, but there's no documentation of how to do so. 

OK, I don't know where else to put this, so I'm putting it here. I've struggled mightily to figure out this API and I hope this feedback will be helpful. Two things:
1. To set the `lifecycle_rules`, you need to set them before you create the bucket:
   
   ```
   b = client.bucket('my-bucket-name')
   b.exists()  # False
   b.lifecycle_rules = [{
       'action': {'type': 'Delete'},
       'condition': {'age': 1},
   }]
   b.create()
   ```
   
   It doesn't seem to be possible to set lifecycle rules for an existing bucket, though I'd love to be proven wrong. If this _is_ possible, it's certainly not documented anywhere I could find. I'd expect that a setter would actually set the values on in Google Cloud or that there would be a `commit` command somewhere to commit the changes (I haven't found this...yet). Since neither is the case, maybe setting these values on an existing bucket should throw an exception? 
2. After you've created a bucket like this, you can test that the creation worked by using the `reload` method on a bucket:
   
   ```
   b = client.bucket('my-bucket-name')
   b.lifecycle_rules  # Returns empty list: [], WTF?
   b.reload()
   b.lifecycle_rules  # Returns the rules from above. Success!
   ```
   
   Since `reload` is _also_ undocumented, this is _extremely_ difficult to figure out, since for all intents and purposes, it looks like your lifecycle rules didn't get created when you check them. The only thing that tipped me off was that there didn't seem to be any network latency when I was trying to get the rules. If I had been on a faster connection, this would have been even harder to detect. 
   
   Do you want another ticket for documenting the `reload` function? I'm new to Google Cloud, but this whole thing is extremely frustrating. There are some big gaps here in documentation and user expectation.

> In fact, the `@property` descriptor explicitly takes the `__doc__` from the getter: any docstring on from the setter or deleter are not discoverable at runtime.

This is basically a duplicate of https://github.com/GoogleCloudPlatform/google-cloud-python/issues/1838.

> that there would be a `commit` command somewhere to commit the changes

It's named `patch`.

@mlissner Thanks for digging deeper!  Addressing a couple of your points:
- Changes made to the client-side `Bucket` object do not implicitly trigger API calls to propagate those changes to the back-end.  This is a typical "distributed system" tradeoff.  Accumulated changes can be pushed to the back-end via `bucket.patch()`.
- As you noted, `Bucket.reload` does not appear in the `Bucket` API docs:  the source of the problem is that `Bucket.reload` (and `Bucket.patch`) are actually inherited from a base class shared with `Blob`, and the Sphinx configuration doesn't specify including inherited members.  I have just opened #2621 to correct this issue.
- Another related issue is that we don't have narrative usage docs for Storage:  they might have made the expected pattern clearer, even in the absence of the inherited members.

Thanks @tseaver and @pdknsk. So I'm seeing three separate issues:
1. Setters undocumented (to be addressed in #1838)
2. Inherited members like `reload` and `patch` are not documented (addressed in #2621, yay!). 
3. No narrative usage docs for Storage. This is still not addressed, and I agree this would make a huge difference. Should we close this and create a new issue for that? 

Following up: setters are now documented for CORS, per #1838, but we still need another PR to fix `lifecycle_rules`, and we still need narrative docs for Storage.


Resolving patch:
diff --git a/storage/google/cloud/storage/bucket.py b/storage/google/cloud/storage/bucket.py
--- a/storage/google/cloud/storage/bucket.py
+++ b/storage/google/cloud/storage/bucket.py
@@ -578,11 +578,14 @@ def id(self):
 
     @property
     def lifecycle_rules(self):
-        """Lifecycle rules configured for this bucket.
+        """Retrieve or set lifecycle rules configured for this bucket.
 
         See https://cloud.google.com/storage/docs/lifecycle and
              https://cloud.google.com/storage/docs/json_api/v1/buckets
 
+        :setter: Set lifestyle rules for this bucket.
+        :getter: Gets the lifestyle rules for this bucket.
+
         :rtype: list(dict)
         :returns: A sequence of mappings describing each lifecycle rule.
         """
@@ -591,6 +594,14 @@ def lifecycle_rules(self):
 
     @lifecycle_rules.setter
     def lifecycle_rules(self, rules):
+        """Set lifestyle rules configured for this bucket.
+
+        See https://cloud.google.com/storage/docs/lifecycle and
+             https://cloud.google.com/storage/docs/json_api/v1/buckets
+
+        :type entries: list of dictionaries
+        :param entries: A sequence of mappings describing each lifecycle rule.
+        """
         self._patch_property('lifecycle', {'rule': rules})
 
     location = _scalar_property('location')
@@ -691,10 +702,13 @@ def self_link(self):
 
     @property
     def storage_class(self):
-        """Retrieve the storage class for the bucket.
+        """Retrieve or set the storage class for the bucket.
 
         See https://cloud.google.com/storage/docs/storage-classes
 
+        :setter: Set the storage class for this bucket.
+        :getter: Gets the the storage class for this bucket.
+
         :rtype: str or ``NoneType``
         :returns: If set, one of "MULTI_REGIONAL", "REGIONAL",
                   "NEARLINE", "COLDLINE", "STANDARD", or
@@ -737,6 +751,9 @@ def versioning_enabled(self):
         See  https://cloud.google.com/storage/docs/object-versioning for
         details.
 
+        :setter: Update whether versioning is enabled for this bucket.
+        :getter: Query whether versioning is enabled for this bucket.
+
         :rtype: bool
         :returns: True if enabled, else False.
         """


Test patch: