Skip to content

Commit 6f98e15

Browse files
committed
Adding lazy loading support for storage default project.
1 parent f47f62c commit 6f98e15

File tree

5 files changed

+225
-91
lines changed

5 files changed

+225
-91
lines changed

gcloud/storage/__init__.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from gcloud.storage._implicit_environ import get_default_bucket
4646
from gcloud.storage._implicit_environ import get_default_connection
4747
from gcloud.storage._implicit_environ import get_default_project
48+
from gcloud.storage._implicit_environ import set_default_project
4849
from gcloud.storage.api import create_bucket
4950
from gcloud.storage.api import get_all_buckets
5051
from gcloud.storage.api import get_bucket
@@ -59,7 +60,6 @@
5960
'https://www.googleapis.com/auth/devstorage.read_write')
6061

6162
_BUCKET_ENV_VAR_NAME = 'GCLOUD_BUCKET_NAME'
62-
_PROJECT_ENV_VAR_NAME = 'GCLOUD_PROJECT'
6363

6464

6565
def set_default_bucket(bucket=None):
@@ -88,25 +88,6 @@ def set_default_bucket(bucket=None):
8888
_implicit_environ._DEFAULTS.bucket = bucket
8989

9090

91-
def set_default_project(project=None):
92-
"""Set default bucket name either explicitly or implicitly as fall-back.
93-
94-
In implicit case, currently only supports enviroment variable but will
95-
support App Engine, Compute Engine and other environments in the future.
96-
97-
Local environment variable used is:
98-
- GCLOUD_PROJECT
99-
100-
:type project: string
101-
:param project: Optional. The project name to use as default.
102-
"""
103-
if project is None:
104-
project = os.getenv(_PROJECT_ENV_VAR_NAME)
105-
106-
if project is not None:
107-
_implicit_environ._DEFAULTS.project = project
108-
109-
11091
def set_default_connection(connection=None):
11192
"""Set default connection either explicitly or implicitly as fall-back.
11293

gcloud/storage/_implicit_environ.py

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,55 @@
1919
"""
2020

2121

22+
import os
23+
24+
from gcloud._helpers import _lazy_property_deco
25+
26+
27+
_PROJECT_ENV_VAR_NAME = 'GCLOUD_PROJECT'
28+
29+
30+
def _get_production_project():
31+
"""Gets the production project if it can be inferred."""
32+
return os.getenv(_PROJECT_ENV_VAR_NAME)
33+
34+
35+
def _determine_default_project(project=None):
36+
"""Determine default project ID explicitly or implicitly as fall-back.
37+
38+
In implicit case, currently only supports enviroment variable but will
39+
support App Engine, Compute Engine and other environments in the future.
40+
41+
Local environment variable used is:
42+
- GCLOUD_PROJECT
43+
44+
:type project: string
45+
:param project: Optional. The project name to use as default.
46+
47+
:rtype: string or ``NoneType``
48+
:returns: Default project if it can be determined.
49+
"""
50+
if project is None:
51+
project = _get_production_project()
52+
53+
return project
54+
55+
56+
def set_default_project(project=None):
57+
"""Set default project either explicitly or implicitly as fall-back.
58+
59+
:type project: string
60+
:param project: Optional. The project name to use as default.
61+
62+
:raises: :class:`EnvironmentError` if no project was found.
63+
"""
64+
project = _determine_default_project(project=project)
65+
if project is not None:
66+
_DEFAULTS.project = project
67+
else:
68+
raise EnvironmentError('No project could be inferred.')
69+
70+
2271
class _DefaultsContainer(object):
2372
"""Container for defaults.
2473
@@ -32,8 +81,16 @@ class _DefaultsContainer(object):
3281
:param connection: Persistent implied connection from environment.
3382
"""
3483

35-
def __init__(self, project=None, bucket=None, connection=None):
36-
self.project = project
84+
@_lazy_property_deco
85+
@staticmethod
86+
def project():
87+
"""Return the implicit default project."""
88+
return _determine_default_project()
89+
90+
def __init__(self, project=None, bucket=None, connection=None,
91+
implicit=False):
92+
if project is not None or not implicit:
93+
self.project = project
3794
self.bucket = bucket
3895
self.connection = connection
3996

@@ -65,4 +122,4 @@ def get_default_connection():
65122
return _DEFAULTS.connection
66123

67124

68-
_DEFAULTS = _DefaultsContainer()
125+
_DEFAULTS = _DefaultsContainer(implicit=True)

gcloud/storage/test___init__.py

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -135,73 +135,6 @@ def test_set_explicit_None_w_env_var_set(self):
135135
self.assertEqual(default_bucket.connection, CONNECTION)
136136

137137

138-
class Test_set_default_project(unittest2.TestCase):
139-
140-
def setUp(self):
141-
from gcloud.storage._testing import _setup_defaults
142-
_setup_defaults(self)
143-
144-
def tearDown(self):
145-
from gcloud.storage._testing import _tear_down_defaults
146-
_tear_down_defaults(self)
147-
148-
def _callFUT(self, project=None):
149-
from gcloud.storage import set_default_project
150-
return set_default_project(project=project)
151-
152-
def _monkey(self, implicit_project):
153-
import os
154-
from gcloud.storage import _PROJECT_ENV_VAR_NAME
155-
from gcloud._testing import _Monkey
156-
environ = {_PROJECT_ENV_VAR_NAME: implicit_project}
157-
return _Monkey(os, getenv=environ.get)
158-
159-
def test_no_env_var_set(self):
160-
from gcloud.storage import _implicit_environ
161-
with self._monkey(None):
162-
self._callFUT()
163-
self.assertEqual(_implicit_environ.get_default_project(), None)
164-
165-
def test_set_from_env_var(self):
166-
from gcloud.storage import _implicit_environ
167-
IMPLICIT_PROJECT = 'IMPLICIT'
168-
with self._monkey(IMPLICIT_PROJECT):
169-
self._callFUT()
170-
self.assertEqual(_implicit_environ.get_default_project(),
171-
IMPLICIT_PROJECT)
172-
173-
def test_set_explicit_w_env_var_set(self):
174-
from gcloud.storage import _implicit_environ
175-
EXPLICIT_PROJECT = 'EXPLICIT'
176-
with self._monkey(None):
177-
self._callFUT(EXPLICIT_PROJECT)
178-
self.assertEqual(_implicit_environ.get_default_project(),
179-
EXPLICIT_PROJECT)
180-
181-
def test_set_explicit_no_env_var_set(self):
182-
from gcloud.storage import _implicit_environ
183-
IMPLICIT_PROJECT = 'IMPLICIT'
184-
EXPLICIT_PROJECT = 'EXPLICIT'
185-
with self._monkey(IMPLICIT_PROJECT):
186-
self._callFUT(EXPLICIT_PROJECT)
187-
self.assertEqual(_implicit_environ.get_default_project(),
188-
EXPLICIT_PROJECT)
189-
190-
def test_set_explicit_None_wo_env_var_set(self):
191-
from gcloud.storage import _implicit_environ
192-
with self._monkey(None):
193-
self._callFUT(None)
194-
self.assertEqual(_implicit_environ.get_default_project(), None)
195-
196-
def test_set_explicit_None_w_env_var_set(self):
197-
from gcloud.storage import _implicit_environ
198-
IMPLICIT_PROJECT = 'IMPLICIT'
199-
with self._monkey(IMPLICIT_PROJECT):
200-
self._callFUT(None)
201-
self.assertEqual(_implicit_environ.get_default_project(),
202-
IMPLICIT_PROJECT)
203-
204-
205138
class Test_set_default_connection(unittest2.TestCase):
206139

207140
def setUp(self):
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Copyright 2014 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest2
16+
17+
18+
class Test__get_production_project(unittest2.TestCase):
19+
20+
def _callFUT(self):
21+
from gcloud.storage import _implicit_environ
22+
return _implicit_environ._get_production_project()
23+
24+
def test_no_value(self):
25+
import os
26+
from gcloud._testing import _Monkey
27+
28+
environ = {}
29+
with _Monkey(os, getenv=environ.get):
30+
project = self._callFUT()
31+
self.assertEqual(project, None)
32+
33+
def test_value_set(self):
34+
import os
35+
from gcloud._testing import _Monkey
36+
from gcloud.storage._implicit_environ import _PROJECT_ENV_VAR_NAME
37+
38+
MOCK_PROJECT = object()
39+
environ = {_PROJECT_ENV_VAR_NAME: MOCK_PROJECT}
40+
with _Monkey(os, getenv=environ.get):
41+
project = self._callFUT()
42+
self.assertEqual(project, MOCK_PROJECT)
43+
44+
45+
class Test__determine_default_project(unittest2.TestCase):
46+
47+
def _callFUT(self, project=None):
48+
from gcloud.storage._implicit_environ import _determine_default_project
49+
return _determine_default_project(project=project)
50+
51+
def _determine_default_helper(self, prod=None, project=None):
52+
from gcloud._testing import _Monkey
53+
from gcloud.storage import _implicit_environ
54+
55+
_callers = []
56+
57+
def prod_mock():
58+
_callers.append('prod_mock')
59+
return prod
60+
61+
patched_methods = {
62+
'_get_production_project': prod_mock,
63+
}
64+
65+
with _Monkey(_implicit_environ, **patched_methods):
66+
returned_project = self._callFUT(project)
67+
68+
return returned_project, _callers
69+
70+
def test_no_value(self):
71+
project, callers = self._determine_default_helper()
72+
self.assertEqual(project, None)
73+
self.assertEqual(callers, ['prod_mock'])
74+
75+
def test_explicit(self):
76+
PROJECT = object()
77+
project, callers = self._determine_default_helper(project=PROJECT)
78+
self.assertEqual(project, PROJECT)
79+
self.assertEqual(callers, [])
80+
81+
def test_prod(self):
82+
PROJECT = object()
83+
project, callers = self._determine_default_helper(prod=PROJECT)
84+
self.assertEqual(project, PROJECT)
85+
self.assertEqual(callers, ['prod_mock'])
86+
87+
88+
class Test_set_default_project(unittest2.TestCase):
89+
90+
def setUp(self):
91+
from gcloud.storage._testing import _setup_defaults
92+
_setup_defaults(self)
93+
94+
def tearDown(self):
95+
from gcloud.storage._testing import _tear_down_defaults
96+
_tear_down_defaults(self)
97+
98+
def _callFUT(self, project=None):
99+
from gcloud.storage._implicit_environ import set_default_project
100+
return set_default_project(project=project)
101+
102+
def test_raises(self):
103+
from gcloud._testing import _Monkey
104+
from gcloud.storage import _implicit_environ
105+
106+
_called_project = []
107+
108+
def mock_determine(project):
109+
_called_project.append(project)
110+
return None
111+
112+
with _Monkey(_implicit_environ,
113+
_determine_default_project=mock_determine):
114+
self.assertRaises(EnvironmentError, self._callFUT)
115+
116+
self.assertEqual(_called_project, [None])
117+
118+
def test_set_correctly(self):
119+
from gcloud._testing import _Monkey
120+
from gcloud.storage import _implicit_environ
121+
122+
self.assertEqual(_implicit_environ._DEFAULTS.project, None)
123+
124+
PROJECT = object()
125+
_called_project = []
126+
127+
def mock_determine(project):
128+
_called_project.append(project)
129+
return PROJECT
130+
131+
with _Monkey(_implicit_environ,
132+
_determine_default_project=mock_determine):
133+
self._callFUT()
134+
135+
self.assertEqual(_implicit_environ._DEFAULTS.project, PROJECT)
136+
self.assertEqual(_called_project, [None])
137+
138+
139+
class Test_lazy_loading(unittest2.TestCase):
140+
141+
def setUp(self):
142+
from gcloud.storage._testing import _setup_defaults
143+
_setup_defaults(self, implicit=True)
144+
145+
def tearDown(self):
146+
from gcloud.storage._testing import _tear_down_defaults
147+
_tear_down_defaults(self)
148+
149+
def test_descriptor_for_project(self):
150+
from gcloud._testing import _Monkey
151+
from gcloud.storage import _implicit_environ
152+
153+
self.assertFalse('project' in _implicit_environ._DEFAULTS.__dict__)
154+
155+
DEFAULT = object()
156+
157+
with _Monkey(_implicit_environ,
158+
_determine_default_project=lambda: DEFAULT):
159+
lazy_loaded = _implicit_environ._DEFAULTS.project
160+
161+
self.assertEqual(lazy_loaded, DEFAULT)
162+
self.assertTrue('project' in _implicit_environ._DEFAULTS.__dict__)

regression/storage.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
from gcloud import exceptions
2121
from gcloud import storage
2222
from gcloud.storage._helpers import _base64_md5hash
23+
from gcloud.storage import _implicit_environ
2324
from gcloud.storage.batch import Batch
2425

2526

2627
HTTP = httplib2.Http()
2728
SHARED_BUCKETS = {}
2829

29-
storage._PROJECT_ENV_VAR_NAME = 'GCLOUD_TESTS_PROJECT_ID'
30+
_implicit_environ._PROJECT_ENV_VAR_NAME = 'GCLOUD_TESTS_PROJECT_ID'
3031
storage.set_defaults()
3132

3233

0 commit comments

Comments
 (0)