Repository: googleapis/google-cloud-python

Issue:
Storage on Standard App Engine causing import error
I just upgraded to gcloud 0.16.0 from 0.13.0
Now, when using the storage API, I'm seeing:

```
client = storage.Client()

File "..../lib/gcloud/storage/client.py", line 53, in __init__
 http=http)

File "..../lib/gcloud/client.py", line 185, in __init__
_ClientProjectMixin.__init__(self, project=project)

File "..../lib/gcloud/client.py", line 141, in __init__
project = self._determine_default(project)

File "..../lib/gcloud/client.py", line 154, in _determine_default
return _determine_default_project(project)

File "..../lib/gcloud/_helpers.py", line 266, in _determine_default_project
project = _default_service_project_id()

File "..../lib/gcloud/_helpers.py", line 191, in _default_service_project_id
full_config_path = os.path.expanduser(DEFAULT_CONFIGURATION_PATH)

File "/base/data/home/runtimes/python27/python27_dist/lib/python2.7/posixpath.py", line 268, in expanduser
import pwd

ImportError: No module named pwd
```

Has the "out-of-the-box" handling for GAE changed?

Storage on Standard App Engine causing import error
I just upgraded to gcloud 0.16.0 from 0.13.0
Now, when using the storage API, I'm seeing:

```
client = storage.Client()

File "..../lib/gcloud/storage/client.py", line 53, in __init__
 http=http)

File "..../lib/gcloud/client.py", line 185, in __init__
_ClientProjectMixin.__init__(self, project=project)

File "..../lib/gcloud/client.py", line 141, in __init__
project = self._determine_default(project)

File "..../lib/gcloud/client.py", line 154, in _determine_default
return _determine_default_project(project)

File "..../lib/gcloud/_helpers.py", line 266, in _determine_default_project
project = _default_service_project_id()

File "..../lib/gcloud/_helpers.py", line 191, in _default_service_project_id
full_config_path = os.path.expanduser(DEFAULT_CONFIGURATION_PATH)

File "/base/data/home/runtimes/python27/python27_dist/lib/python2.7/posixpath.py", line 268, in expanduser
import pwd

ImportError: No module named pwd
```

Has the "out-of-the-box" handling for GAE changed?



Discussion:
@ndenny, thanks for reporting! The `pwd` module is actually [disabled in GAE](https://cloud.google.com/appengine/kb/?csw=1#libraries).

I added a helper method to try and determine the project ID from default locations if you're using it locally.
I apologize for the oversight!

You should be ok to update to version 0.15.0 and I'll try and find a solution for this ASAP.

@ndenny, thanks for reporting! The `pwd` module is actually [disabled in GAE](https://cloud.google.com/appengine/kb/?csw=1#libraries).

I added a helper method to try and determine the project ID from default locations if you're using it locally.
I apologize for the oversight!

You should be ok to update to version 0.15.0 and I'll try and find a solution for this ASAP.


Resolving patch:
diff --git a/gcloud/_helpers.py b/gcloud/_helpers.py
--- a/gcloud/_helpers.py
+++ b/gcloud/_helpers.py
@@ -188,12 +188,18 @@ def _default_service_project_id():
     :rtype: str or ``NoneType``
     :returns: Project-ID from default configuration file else ``None``
     """
-    full_config_path = os.path.expanduser(DEFAULT_CONFIGURATION_PATH)
+    search_paths = []
+    # Workaround for GAE not supporting pwd which is used by expanduser.
+    try:
+        search_paths.append(os.path.expanduser(DEFAULT_CONFIGURATION_PATH))
+    except ImportError:
+        pass
     win32_config_path = os.path.join(os.getenv('APPDATA', ''),
                                      'gcloud', 'configurations',
                                      'config_default')
+    search_paths.append(win32_config_path)
     config = configparser.RawConfigParser()
-    config.read([full_config_path, win32_config_path])
+    config.read(search_paths)
 
     if config.has_section('core'):
         return config.get('core', 'project')


Test patch:
diff --git a/gcloud/test__helpers.py b/gcloud/test__helpers.py
--- a/gcloud/test__helpers.py
+++ b/gcloud/test__helpers.py
@@ -211,6 +211,7 @@ def callFUT(self, project_id=None):
 
         def mock_expanduser(path=''):
             if project_id and path.startswith('~'):
+                __import__('pwd')  # Simulate actual expanduser imports.
                 return self.temp_config_file
             return ''
 
@@ -221,6 +222,15 @@ def test_read_from_cli_info(self):
         project_id = self.callFUT('test-project-id')
         self.assertEqual('test-project-id', project_id)
 
+    def test_gae_without_expanduser(self):
+        import sys
+        try:
+            sys.modules['pwd'] = None  # Blocks pwd from being imported.
+            project_id = self.callFUT('test-project-id')
+            self.assertEqual(None, project_id)
+        finally:
+            del sys.modules['pwd']  # Unblocks importing of pwd.
+
     def test_info_value_not_present(self):
         project_id = self.callFUT()
         self.assertEqual(None, project_id)