Repository: googleapis/google-cloud-python

Issue:
Storage: str() metadata for bucket and blob
Surfaced by GCS team.

The Cloud Storage library must stringify metadata sent in bucket and blob.

Here's a known issue from not doing so:
```python
from google.cloud import storage
client = storage.Client(project="my-project")
my_bucket = client.get_bucket("my-bucket")
my_blob = my_bucket.get_blob("my-blob")
my_blob.metadata["bad"] = float('nan')
my_blob.patch()
```

`myblob.patch()` will send an unquoted `NaN`, which is a bug in the Cloud Storage API that the client should no longer depend on. 


Discussion:

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
@@ -1911,6 +1911,7 @@ def metadata(self, value):
         :type value: dict
         :param value: (Optional) The blob metadata to set.
         """
+        value = {k: str(v) for k, v in value.items()}
         self._patch_property("metadata", value)
 
     @property
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
@@ -1366,6 +1366,7 @@ def labels(self, mapping):
         existing = set([k for k in self.labels.keys()])
         incoming = set([k for k in mapping.keys()])
         self._label_removals = self._label_removals.union(existing.difference(incoming))
+        mapping = {k: str(v) for k, v in mapping.items()}
 
         # Actually update the labels on the object.
         self._patch_property("labels", copy.deepcopy(mapping))


Test patch:
diff --git a/storage/tests/unit/test_blob.py b/storage/tests/unit/test_blob.py
--- a/storage/tests/unit/test_blob.py
+++ b/storage/tests/unit/test_blob.py
@@ -2913,6 +2913,16 @@ def test_metadata_setter(self):
         blob.metadata = METADATA
         self.assertEqual(blob.metadata, METADATA)
 
+    def test_metadata_setter_w_nan(self):
+        BLOB_NAME = "blob-name"
+        METADATA = {"foo": float("nan")}
+        bucket = _Bucket()
+        blob = self._make_one(BLOB_NAME, bucket=bucket)
+        self.assertIsNone(blob.metadata)
+        blob.metadata = METADATA
+        value = blob.metadata["foo"]
+        self.assertIsInstance(value, str)
+
     def test_metageneration(self):
         BUCKET = object()
         METAGENERATION = 42
diff --git a/storage/tests/unit/test_bucket.py b/storage/tests/unit/test_bucket.py
--- a/storage/tests/unit/test_bucket.py
+++ b/storage/tests/unit/test_bucket.py
@@ -1651,6 +1651,16 @@ def test_labels_setter(self):
         self.assertIsNot(bucket._properties["labels"], LABELS)
         self.assertIn("labels", bucket._changes)
 
+    def test_labels_setter_with_nan(self):
+        NAME = "name"
+        LABELS = {"color": "red", "foo": float("nan")}
+        bucket = self._make_one(name=NAME)
+
+        self.assertEqual(bucket.labels, {})
+        bucket.labels = LABELS
+        value = bucket.labels["foo"]
+        self.assertIsInstance(value, str)
+
     def test_labels_setter_with_removal(self):
         # Make sure the bucket labels look correct and follow the expected
         # public structure.