Repository: googleapis/google-cloud-python

Issue:
Documentation Unclear: "If the property is not set locally, returns None."
The [Google Storage Blob documentation](https://googlecloudplatform.github.io/google-cloud-python/latest/storage/blobs.html) says that almost all properties return the value `None` if the "property is not set locally." However, there is no documentation explaining how properties are set/unset locally or when that occurs. 

This is confusing, there should be a preamble explaining when local Blob properties are set locally.

### Motivation

I ran into this because I started seeing some of these values return `None` in my project in unexpected circumstances (there may be another bug there), but when I went to the documentation, it did not shed much light on why or when these properties should be returning `None`.


Discussion:
It happens to me that all the blob properties return `None`, except if I call the method `update()` first, then I've got the whole properties set (`updated` property is set to actual `datetime`), but this properties are accessible only during the execution. 
@laldonza the behavior you are seeing is by design:  the blob instance does not make an API request to load the property values implicitly.
@tseaver Ok, thanks for the explanation. What about the `updated` property? It gets the time when the `update()` method is called, wouldn't be better to be the last_modified time?
@laldonza `Blob.updated` is fetched from the JSON resource returned by the server.  If the blob has never loaded that resource (newly constructed instance, no `reload` or other API call made), its `updated` property returns `None`, like most of the others based on the JSON resource.
What you want is to call `blob.reload()`, which fetches the server resource without sending any updates to the server.

Resolving patch:
diff --git a/storage/google/cloud/storage/blob.py b/storage/google/cloud/storage/blob.py
--- a/storage/google/cloud/storage/blob.py
+++ b/storage/google/cloud/storage/blob.py
@@ -1501,8 +1501,6 @@ def update_storage_class(self, new_class, client=None):
 
     See `RFC 7234`_ and `API reference docs`_.
 
-    If the property is not set locally, returns :data:`None`.
-
     :rtype: str or ``NoneType``
 
     .. _RFC 7234: https://tools.ietf.org/html/rfc7234#section-5.2
@@ -1513,8 +1511,6 @@ def update_storage_class(self, new_class, client=None):
 
     See `RFC 6266`_ and `API reference docs`_.
 
-    If the property is not set locally, returns :data:`None`.
-
     :rtype: str or ``NoneType``
 
     .. _RFC 6266: https://tools.ietf.org/html/rfc7234#section-5.2
@@ -1525,8 +1521,6 @@ def update_storage_class(self, new_class, client=None):
 
     See `RFC 7231`_ and `API reference docs`_.
 
-    If the property is not set locally, returns ``None``.
-
     :rtype: str or ``NoneType``
 
     .. _RFC 7231: https://tools.ietf.org/html/rfc7231#section-3.1.2.2
@@ -1537,8 +1531,6 @@ def update_storage_class(self, new_class, client=None):
 
     See `BCP47`_ and `API reference docs`_.
 
-    If the property is not set locally, returns :data:`None`.
-
     :rtype: str or ``NoneType``
 
     .. _BCP47: https://tools.ietf.org/html/bcp47
@@ -1549,8 +1541,6 @@ def update_storage_class(self, new_class, client=None):
 
     See `RFC 2616`_ and `API reference docs`_.
 
-    If the property is not set locally, returns :data:`None`.
-
     :rtype: str or ``NoneType``
 
     .. _RFC 2616: https://tools.ietf.org/html/rfc2616#section-14.17
@@ -1561,7 +1551,7 @@ def update_storage_class(self, new_class, client=None):
 
     See `RFC 4960`_ and `API reference docs`_.
 
-    If the property is not set locally, returns :data:`None`.
+    If not set before upload, the server will compute the hash.
 
     :rtype: str or ``NoneType``
 
@@ -1576,8 +1566,9 @@ def component_count(self):
 
         :rtype: int or ``NoneType``
         :returns: The component count (in case of a composed object) or
-                  ``None`` if the property is not set locally. This property
-                  will not be set on objects not created via ``compose``.
+                  ``None`` if the blob's resource has not been loaded from
+                  the server.  This property will not be set on objects
+                  not created via ``compose``.
         """
         component_count = self._properties.get('componentCount')
         if component_count is not None:
@@ -1590,7 +1581,8 @@ def etag(self):
         See `RFC 2616 (etags)`_ and `API reference docs`_.
 
         :rtype: str or ``NoneType``
-        :returns: The blob etag or ``None`` if the property is not set locally.
+        :returns: The blob etag or ``None`` if the blob's resource has not
+                  been loaded from the server.
 
         .. _RFC 2616 (etags): https://tools.ietf.org/html/rfc2616#section-3.11
         """
@@ -1603,8 +1595,8 @@ def generation(self):
         See https://cloud.google.com/storage/docs/json_api/v1/objects
 
         :rtype: int or ``NoneType``
-        :returns: The generation of the blob or ``None`` if the property
-                  is not set locally.
+        :returns: The generation of the blob or ``None`` if the blob's
+                  resource has not been loaded from the server.
         """
         generation = self._properties.get('generation')
         if generation is not None:
@@ -1616,9 +1608,11 @@ def id(self):
 
         See https://cloud.google.com/storage/docs/json_api/v1/objects
 
+        The ID consists of the bucket name, object name, and generation number.
+
         :rtype: str or ``NoneType``
-        :returns: The ID of the blob or ``None`` if the property is not
-                  set locally.
+        :returns: The ID of the blob or ``None`` if the blob's
+                  resource has not been loaded from the server.
         """
         return self._properties.get('id')
 
@@ -1627,7 +1621,7 @@ def id(self):
 
     See `RFC 1321`_ and `API reference docs`_.
 
-    If the property is not set locally, returns ``None``.
+    If not set before upload, the server will compute the hash.
 
     :rtype: str or ``NoneType``
 
@@ -1641,8 +1635,8 @@ def media_link(self):
         See https://cloud.google.com/storage/docs/json_api/v1/objects
 
         :rtype: str or ``NoneType``
-        :returns: The media link for the blob or ``None`` if the property is
-                  not set locally.
+        :returns: The media link for the blob or ``None`` if the blob's
+                  resource has not been loaded from the server.
         """
         return self._properties.get('mediaLink')
 
@@ -1659,7 +1653,7 @@ def metadata(self):
 
         :rtype: dict or ``NoneType``
         :returns: The metadata associated with the blob or ``None`` if the
-                  property is not set locally.
+                  property is not set.
         """
         return copy.deepcopy(self._properties.get('metadata'))
 
@@ -1681,8 +1675,8 @@ def metageneration(self):
         See https://cloud.google.com/storage/docs/json_api/v1/objects
 
         :rtype: int or ``NoneType``
-        :returns: The metageneration of the blob or ``None`` if the property
-                  is not set locally.
+        :returns: The metageneration of the blob or ``None`` if the blob's
+                  resource has not been loaded from the server.
         """
         metageneration = self._properties.get('metageneration')
         if metageneration is not None:
@@ -1695,8 +1689,8 @@ def owner(self):
         See https://cloud.google.com/storage/docs/json_api/v1/objects
 
         :rtype: dict or ``NoneType``
-        :returns: Mapping of owner's role/ID. If the property is not set
-                  locally, returns ``None``.
+        :returns: Mapping of owner's role/ID, or ``None`` if the blob's
+                  resource has not been loaded from the server.
         """
         return copy.deepcopy(self._properties.get('owner'))
 
@@ -1707,8 +1701,8 @@ def self_link(self):
         See https://cloud.google.com/storage/docs/json_api/v1/objects
 
         :rtype: str or ``NoneType``
-        :returns: The self link for the blob or ``None`` if the property is
-                  not set locally.
+        :returns: The self link for the blob or ``None`` if the blob's
+                  resource has not been loaded from the server.
         """
         return self._properties.get('selfLink')
 
@@ -1719,8 +1713,8 @@ def size(self):
         See https://cloud.google.com/storage/docs/json_api/v1/objects
 
         :rtype: int or ``NoneType``
-        :returns: The size of the blob or ``None`` if the property
-                  is not set locally.
+        :returns: The size of the blob or ``None`` if the blob's
+                  resource has not been loaded from the server.
         """
         size = self._properties.get('size')
         if size is not None:
@@ -1732,7 +1726,8 @@ def kms_key_name(self):
 
         :rtype: str or ``NoneType``
         :returns:
-            The resource name or ``None`` if the property is not set locally.
+            The resource name or ``None`` if no Cloud KMS key was used,
+            or the blob's resource has not been loaded from the server.
         """
         return self._properties.get('kmsKeyName')
 
@@ -1760,7 +1755,8 @@ def time_deleted(self):
 
         :rtype: :class:`datetime.datetime` or ``NoneType``
         :returns: Datetime object parsed from RFC3339 valid timestamp, or
-                  ``None`` if the property is not set locally. If the blob has
+                  ``None`` if the blob's resource has not been loaded from
+                  the server (see :meth:`reload`). If the blob has
                   not been deleted, this will never be set.
         """
         value = self._properties.get('timeDeleted')
@@ -1775,7 +1771,8 @@ def time_created(self):
 
         :rtype: :class:`datetime.datetime` or ``NoneType``
         :returns: Datetime object parsed from RFC3339 valid timestamp, or
-                  ``None`` if the property is not set locally.
+                  ``None`` if the blob's resource has not been loaded from
+                  the server (see :meth:`reload`).
         """
         value = self._properties.get('timeCreated')
         if value is not None:
@@ -1789,7 +1786,8 @@ def updated(self):
 
         :rtype: :class:`datetime.datetime` or ``NoneType``
         :returns: Datetime object parsed from RFC3339 valid timestamp, or
-                  ``None`` if the property is not set locally.
+                  ``None`` if the blob's resource has not been loaded from
+                  the server (see :meth:`reload`).
         """
         value = self._properties.get('updated')
         if value is not None:
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
@@ -876,8 +876,8 @@ def etag(self):
              https://cloud.google.com/storage/docs/json_api/v1/buckets
 
         :rtype: str or ``NoneType``
-        :returns: The bucket etag or ``None`` if the property is not
-                  set locally.
+        :returns: The bucket etag or ``None`` if the bucket's
+                  resource has not been loaded from the server.
         """
         return self._properties.get('etag')
 
@@ -888,8 +888,8 @@ def id(self):
         See https://cloud.google.com/storage/docs/json_api/v1/buckets
 
         :rtype: str or ``NoneType``
-        :returns: The ID of the bucket or ``None`` if the property is not
-                  set locally.
+        :returns: The ID of the bucket or ``None`` if the bucket's
+                  resource has not been loaded from the server.
         """
         return self._properties.get('id')
 
@@ -938,10 +938,13 @@ def lifecycle_rules(self, rules):
     location = _scalar_property('location')
     """Retrieve location configured for this bucket.
 
+    This can only be set at bucket **creation** time.
+
     See https://cloud.google.com/storage/docs/json_api/v1/buckets and
     https://cloud.google.com/storage/docs/bucket-locations
 
-    If the property is not set locally, returns ``None``.
+    Returns ``None`` if the property has not been set before creation,
+    or if the bucket's resource has not been loaded from the server.
 
     :rtype: str or ``NoneType``
     """
@@ -986,8 +989,8 @@ def metageneration(self):
         See https://cloud.google.com/storage/docs/json_api/v1/buckets
 
         :rtype: int or ``NoneType``
-        :returns: The metageneration of the bucket or ``None`` if the property
-                  is not set locally.
+        :returns: The metageneration of the bucket or ``None`` if the bucket's
+                  resource has not been loaded from the server.
         """
         metageneration = self._properties.get('metageneration')
         if metageneration is not None:
@@ -1000,8 +1003,8 @@ def owner(self):
         See https://cloud.google.com/storage/docs/json_api/v1/buckets
 
         :rtype: dict or ``NoneType``
-        :returns: Mapping of owner's role/ID. If the property is not set
-                  locally, returns ``None``.
+        :returns: Mapping of owner's role/ID. Returns ``None`` if the bucket's
+                  resource has not been loaded from the server.
         """
         return copy.deepcopy(self._properties.get('owner'))
 
@@ -1012,8 +1015,8 @@ def project_number(self):
         See https://cloud.google.com/storage/docs/json_api/v1/buckets
 
         :rtype: int or ``NoneType``
-        :returns: The project number that owns the bucket or ``None`` if the
-                  property is not set locally.
+        :returns: The project number that owns the bucket or ``None`` if
+                  the bucket's resource has not been loaded from the server.
         """
         project_number = self._properties.get('projectNumber')
         if project_number is not None:
@@ -1026,8 +1029,8 @@ def self_link(self):
         See https://cloud.google.com/storage/docs/json_api/v1/buckets
 
         :rtype: str or ``NoneType``
-        :returns: The self link for the bucket or ``None`` if the property is
-                  not set locally.
+        :returns: The self link for the bucket or ``None`` if
+                  the bucket's resource has not been loaded from the server.
         """
         return self._properties.get('selfLink')
 
@@ -1069,7 +1072,8 @@ def time_created(self):
 
         :rtype: :class:`datetime.datetime` or ``NoneType``
         :returns: Datetime object parsed from RFC3339 valid timestamp, or
-                  ``None`` if the property is not set locally.
+                  ``None`` if the bucket's resource has not been loaded
+                  from the server.
         """
         value = self._properties.get('timeCreated')
         if value is not None:


Test patch: