Skip to content

Commit 99af3ad

Browse files
committed
Merge pull request #735 from dhermes/default-project-lazy-loaded
Adding lazy loading behavior for default project (storage).
2 parents 8178991 + 99a16a2 commit 99af3ad

File tree

12 files changed

+366
-215
lines changed

12 files changed

+366
-215
lines changed

gcloud/_localstack.py renamed to gcloud/_helpers.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,46 @@ def top(self):
5959
"""
6060
if len(self._stack) > 0:
6161
return self._stack[-1]
62+
63+
64+
class _LazyProperty(object):
65+
"""Descriptor for lazy loaded property.
66+
67+
This follows the reify pattern: lazy evaluation and then replacement
68+
after evaluation.
69+
70+
:type name: string
71+
:param name: The name of the attribute / property being evaluated.
72+
73+
:type deferred_callable: callable that takes no arguments
74+
:param deferred_callable: The function / method used to evaluate the
75+
property.
76+
"""
77+
78+
def __init__(self, name, deferred_callable):
79+
self._name = name
80+
self._deferred_callable = deferred_callable
81+
82+
def __get__(self, obj, objtype):
83+
if obj is None:
84+
return self
85+
86+
setattr(obj, self._name, self._deferred_callable())
87+
return getattr(obj, self._name)
88+
89+
90+
def _lazy_property_deco(deferred_callable):
91+
"""Decorator a method to create a :class:`_LazyProperty`.
92+
93+
:type deferred_callable: callable that takes no arguments
94+
:param deferred_callable: The function / method used to evaluate the
95+
property.
96+
97+
:rtype: :class:`_LazyProperty`.
98+
:returns: A lazy property which defers the deferred_callable.
99+
"""
100+
if isinstance(deferred_callable, staticmethod):
101+
# H/T: http://stackoverflow.com/a/9527450/1068170
102+
# For Python2.7+ deferred_callable.__func__ would suffice.
103+
deferred_callable = deferred_callable.__get__(True)
104+
return _LazyProperty(deferred_callable.__name__, deferred_callable)

gcloud/datastore/_implicit_environ.py

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
except ImportError:
2929
app_identity = None
3030

31+
from gcloud._helpers import _lazy_property_deco
3132
from gcloud import credentials
3233
from gcloud.datastore.connection import Connection
3334

@@ -201,49 +202,6 @@ def get_default_connection():
201202
return _DEFAULTS.connection
202203

203204

204-
class _LazyProperty(object):
205-
"""Descriptor for lazy loaded property.
206-
207-
This follows the reify pattern: lazy evaluation and then replacement
208-
after evaluation.
209-
210-
:type name: string
211-
:param name: The name of the attribute / property being evaluated.
212-
213-
:type deferred_callable: callable that takes no arguments
214-
:param deferred_callable: The function / method used to evaluate the
215-
property.
216-
"""
217-
218-
def __init__(self, name, deferred_callable):
219-
self._name = name
220-
self._deferred_callable = deferred_callable
221-
222-
def __get__(self, obj, objtype):
223-
if obj is None or objtype is not _DefaultsContainer:
224-
return self
225-
226-
setattr(obj, self._name, self._deferred_callable())
227-
return getattr(obj, self._name)
228-
229-
230-
def _lazy_property_deco(deferred_callable):
231-
"""Decorator a method to create a :class:`_LazyProperty`.
232-
233-
:type deferred_callable: callable that takes no arguments
234-
:param deferred_callable: The function / method used to evaluate the
235-
property.
236-
237-
:rtype: :class:`_LazyProperty`.
238-
:returns: A lazy property which defers the deferred_callable.
239-
"""
240-
if isinstance(deferred_callable, staticmethod):
241-
# H/T: http://stackoverflow.com/a/9527450/1068170
242-
# For Python2.7+ deferred_callable.__func__ would suffice.
243-
deferred_callable = deferred_callable.__get__(True)
244-
return _LazyProperty(deferred_callable.__name__, deferred_callable)
245-
246-
247205
class _DefaultsContainer(object):
248206
"""Container for defaults.
249207

gcloud/datastore/batch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
"""Create / interact with a batch of updates / deletes."""
1616

17-
from gcloud._localstack import _LocalStack
17+
from gcloud._helpers import _LocalStack
1818
from gcloud.datastore import _implicit_environ
1919
from gcloud.datastore import helpers
2020
from gcloud.datastore.key import _dataset_ids_equal

gcloud/datastore/test__implicit_environ.py

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -315,29 +315,6 @@ def mock_determine(dataset_id):
315315
self.assertEqual(_called_dataset_id, [None])
316316

317317

318-
class Test__lazy_property_deco(unittest2.TestCase):
319-
320-
def _callFUT(self, deferred_callable):
321-
from gcloud.datastore._implicit_environ import _lazy_property_deco
322-
return _lazy_property_deco(deferred_callable)
323-
324-
def test_on_function(self):
325-
def test_func():
326-
pass # pragma: NO COVER never gets called
327-
328-
lazy_prop = self._callFUT(test_func)
329-
self.assertTrue(lazy_prop._deferred_callable is test_func)
330-
self.assertEqual(lazy_prop._name, 'test_func')
331-
332-
def test_on_staticmethod(self):
333-
def test_func():
334-
pass # pragma: NO COVER never gets called
335-
336-
lazy_prop = self._callFUT(staticmethod(test_func))
337-
self.assertTrue(lazy_prop._deferred_callable is test_func)
338-
self.assertEqual(lazy_prop._name, 'test_func')
339-
340-
341318
class Test_lazy_loading(unittest2.TestCase):
342319

343320
def setUp(self):
@@ -348,19 +325,6 @@ def tearDown(self):
348325
from gcloud.datastore._testing import _tear_down_defaults
349326
_tear_down_defaults(self)
350327

351-
def test_prop_on_wrong_class(self):
352-
from gcloud.datastore._implicit_environ import _LazyProperty
353-
354-
# Don't actually need a callable for ``method`` since
355-
# __get__ will just return ``self`` in this test.
356-
data_prop = _LazyProperty('dataset_id', None)
357-
358-
class FakeEnv(object):
359-
dataset_id = data_prop
360-
361-
self.assertTrue(FakeEnv.dataset_id is data_prop)
362-
self.assertTrue(FakeEnv().dataset_id is data_prop)
363-
364328
def test_descriptor_for_dataset_id(self):
365329
from gcloud._testing import _Monkey
366330
from gcloud.datastore import _implicit_environ

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/batch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
import six
2727

28-
from gcloud._localstack import _LocalStack
28+
from gcloud._helpers import _LocalStack
2929
from gcloud.storage import _implicit_environ
3030
from gcloud.storage.connection import Connection
3131

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):

0 commit comments

Comments
 (0)