Repository: googleapis/google-cloud-python

Issue:
gcloud._helpers._compute_engine_id should return a str (Python 3)
From https://github.com/GoogleCloudPlatform/google-cloud-datastore/issues/92:

I stumbled upon this while deploying my app for the first time on GKE/GCE.

I had to explicitly specify `project` when creating a client: `gcloud.datastore.Client(project="my-project")`, otherwise I just got an `gcloud.exceptions.ServiceUnavailable: 503 Backend Error`.

First I thought the problem would be that autodetecting the project-id didn't work, but `gcloud.datastore.Client(credentials=credentials, project=project)` returns my project-id, be it as `bytes` instead of `str`.

I quickly verified that using `bytes` as a project-id does not work, i.e. `gcloud.datastore.Client(project=b"my-project")` resulted in that neat `503 Backend Error`.



Discussion:
Thanks. This has come up before (I'll try to track down where). I wish there was an easy way to run system tests on GCE and GAE.

#1470 was when it came up. "Fixed" in #1505

@pcostell What version are you using? It seems like this should be all fixed in `datastore-v1beta3` branch

sorry I copied this from the Datastore issue tracker. @flunderpero, can you share what gcloud version you are using?

DERP. Sorry I didn't even notice that

I'm using gcloud 0.11.0 installed from PyPi.

Thanks. Right now the [datastore `Client`](https://github.com/GoogleCloudPlatform/gcloud-python/blob/98edc644049779927c981fe69eb76d7066540f0c/gcloud/datastore/client.py#L158) doesn't use the [project mix-in](https://github.com/GoogleCloudPlatform/gcloud-python/blob/98edc644049779927c981fe69eb76d7066540f0c/gcloud/client.py#L127). I "imagined" that the [`Client` in the `datastore-v1beta3` branch](https://github.com/GoogleCloudPlatform/gcloud-python/blob/f9e4efc3feea3174e203f3aa8b3bb05a0b4d73ee/gcloud/datastore/client.py#L144) did "the right thing", but that hasn't been updated either.

Thanks for poking me into noticing this change was needed!


Resolving patch:
diff --git a/gcloud/client.py b/gcloud/client.py
--- a/gcloud/client.py
+++ b/gcloud/client.py
@@ -132,21 +132,26 @@ class _ClientProjectMixin(object):
                     passed falls back to the default inferred from the
                     environment.
 
-    :raises: :class:`ValueError` if the project is neither passed in nor
-             set in the environment.
+    :raises: :class:`EnvironmentError` if the project is neither passed in nor
+             set in the environment. :class:`ValueError` if the project value
+             is invalid.
     """
 
     def __init__(self, project=None):
-        project = _determine_default_project(project)
+        project = self._determine_default(project)
         if project is None:
-            raise ValueError('Project was not passed and could not be '
-                             'determined from the environment.')
+            raise EnvironmentError('Project was not passed and could not be '
+                                   'determined from the environment.')
         if isinstance(project, six.binary_type):
             project = project.decode('utf-8')
         if not isinstance(project, six.string_types):
             raise ValueError('Project must be a string.')
         self.project = project
 
+    @staticmethod
+    def _determine_default(project):
+        return _determine_default_project(project)
+
 
 class JSONClient(Client, _ClientProjectMixin):
     """Client to for Google JSON-based API.
diff --git a/gcloud/datastore/client.py b/gcloud/datastore/client.py
--- a/gcloud/datastore/client.py
+++ b/gcloud/datastore/client.py
@@ -18,6 +18,7 @@
 from gcloud._helpers import _LocalStack
 from gcloud._helpers import _app_engine_id
 from gcloud._helpers import _compute_engine_id
+from gcloud.client import _ClientProjectMixin
 from gcloud.client import Client as _BaseClient
 from gcloud.datastore import helpers
 from gcloud.datastore.connection import Connection
@@ -155,7 +156,7 @@ def _extended_lookup(connection, project, key_pbs,
     return results
 
 
-class Client(_BaseClient):
+class Client(_BaseClient, _ClientProjectMixin):
     """Convenience wrapper for invoking APIs/factories w/ a project.
 
     :type project: string
@@ -180,14 +181,15 @@ class Client(_BaseClient):
 
     def __init__(self, project=None, namespace=None,
                  credentials=None, http=None):
-        project = _determine_default_project(project)
-        if project is None:
-            raise EnvironmentError('Project could not be inferred.')
-        self.project = project
+        _ClientProjectMixin.__init__(self, project=project)
         self.namespace = namespace
         self._batch_stack = _LocalStack()
         super(Client, self).__init__(credentials, http)
 
+    @staticmethod
+    def _determine_default(project):
+        return _determine_default_project(project)
+
     def _push_batch(self, batch):
         """Push a batch/transaction onto our stack.
 


Test patch:
diff --git a/gcloud/test_client.py b/gcloud/test_client.py
--- a/gcloud/test_client.py
+++ b/gcloud/test_client.py
@@ -171,7 +171,7 @@ def mock_determine_proj(project):
             return None
 
         with _Monkey(client, _determine_default_project=mock_determine_proj):
-            self.assertRaises(ValueError, self._makeOne)
+            self.assertRaises(EnvironmentError, self._makeOne)
 
         self.assertEqual(FUNC_CALLS, [(None, '_determine_default_project')])