From c590c565041b2b4e8e7a520f2306e314083ead7f Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 20 Mar 2015 12:28:18 -0400 Subject: [PATCH] Hoist project detection/setup into 'gcloud._helpers'. Second bullet for #741. --- gcloud/_helpers.py | 80 ++++++++++++ gcloud/_testing.py | 17 +++ gcloud/storage/__init__.py | 4 +- gcloud/storage/_implicit_environ.py | 78 +----------- gcloud/storage/api.py | 2 +- gcloud/storage/test__implicit_environ.py | 149 ++--------------------- gcloud/storage/test_api.py | 12 +- gcloud/test__helpers.py | 145 ++++++++++++++++++++++ regression/storage.py | 4 +- 9 files changed, 270 insertions(+), 221 deletions(-) diff --git a/gcloud/_helpers.py b/gcloud/_helpers.py index 3a469f300bf7..ab730c947b2a 100644 --- a/gcloud/_helpers.py +++ b/gcloud/_helpers.py @@ -15,6 +15,7 @@ This module is not part of the public API surface of `gcloud`. """ +import os import socket try: @@ -156,3 +157,82 @@ def _compute_engine_id(): pass finally: connection.close() + + +_PROJECT_ENV_VAR_NAME = 'GCLOUD_PROJECT' + + +def _get_production_project(): + """Gets the production project if it can be inferred.""" + return os.getenv(_PROJECT_ENV_VAR_NAME) + + +def _determine_default_project(project=None): + """Determine default project ID explicitly or implicitly as fall-back. + + In implicit case, currently only supports enviroment variable but will + support App Engine, Compute Engine and other environments in the future. + + Local environment variable used is: + - GCLOUD_PROJECT + + :type project: string + :param project: Optional. The project name to use as default. + + :rtype: string or ``NoneType`` + :returns: Default project if it can be determined. + """ + if project is None: + project = _get_production_project() + + return project + + +def set_default_project(project=None): + """Set default project either explicitly or implicitly as fall-back. + + :type project: string + :param project: Optional. The project name to use as default. + + :raises: :class:`EnvironmentError` if no project was found. + """ + project = _determine_default_project(project=project) + if project is not None: + _DEFAULTS.project = project + else: + raise EnvironmentError('No project could be inferred.') + + +def get_default_project(): + """Get default project. + + :rtype: string or ``NoneType`` + :returns: The default project if one has been set. + """ + return _DEFAULTS.project + + +class _DefaultsContainer(object): + """Container for defaults. + + :type project: string + :param project: Persistent implied project from environment. + + :type implicit: boolean + :param implicit: if False, assign the instance's ``project`` attribute + unconditionally; otherwise, assign it only if the + value is not None. + """ + + @_lazy_property_deco + @staticmethod + def project(): + """Return the implicit default project.""" + return _determine_default_project() + + def __init__(self, project=None, implicit=False): + if project is not None or not implicit: + self.project = project + + +_DEFAULTS = _DefaultsContainer(implicit=True) diff --git a/gcloud/_testing.py b/gcloud/_testing.py index 824214482e20..18d21b19b358 100644 --- a/gcloud/_testing.py +++ b/gcloud/_testing.py @@ -14,6 +14,9 @@ """Shared testing utilities.""" +from gcloud import _helpers +from gcloud._helpers import _DefaultsContainer + class _Monkey(object): # context-manager for replacing module names in the scope of a test. @@ -30,3 +33,17 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): for key, value in self.to_restore.items(): setattr(self.module, key, value) + + +def _monkey_defaults(*args, **kwargs): + mock_defaults = _DefaultsContainer(*args, **kwargs) + return _Monkey(_helpers, _DEFAULTS=mock_defaults) + + +def _setup_defaults(test_case, *args, **kwargs): + test_case._replaced_defaults = _helpers._DEFAULTS + _helpers._DEFAULTS = _DefaultsContainer(*args, **kwargs) + + +def _tear_down_defaults(test_case): + _helpers._DEFAULTS = test_case._replaced_defaults diff --git a/gcloud/storage/__init__.py b/gcloud/storage/__init__.py index ffb42c53f2b9..1040466863e2 100644 --- a/gcloud/storage/__init__.py +++ b/gcloud/storage/__init__.py @@ -41,11 +41,11 @@ import os from gcloud import credentials +from gcloud._helpers import get_default_project +from gcloud._helpers import set_default_project from gcloud.storage import _implicit_environ from gcloud.storage._implicit_environ import get_default_bucket from gcloud.storage._implicit_environ import get_default_connection -from gcloud.storage._implicit_environ import get_default_project -from gcloud.storage._implicit_environ import set_default_project from gcloud.storage.api import create_bucket from gcloud.storage.api import get_all_buckets from gcloud.storage.api import get_bucket diff --git a/gcloud/storage/_implicit_environ.py b/gcloud/storage/_implicit_environ.py index 4bfbc46a214a..6e4c5b57a7b2 100644 --- a/gcloud/storage/_implicit_environ.py +++ b/gcloud/storage/_implicit_environ.py @@ -14,66 +14,14 @@ """Module to provide implicit behavior based on enviroment. -Allows the storage package to infer the current project, default bucket -and connection from the enviroment. +Allows the storage package to infer the default bucket and connection +from the enviroment. """ -import os - -from gcloud._helpers import _lazy_property_deco - - -_PROJECT_ENV_VAR_NAME = 'GCLOUD_PROJECT' - - -def _get_production_project(): - """Gets the production project if it can be inferred.""" - return os.getenv(_PROJECT_ENV_VAR_NAME) - - -def _determine_default_project(project=None): - """Determine default project ID explicitly or implicitly as fall-back. - - In implicit case, currently only supports enviroment variable but will - support App Engine, Compute Engine and other environments in the future. - - Local environment variable used is: - - GCLOUD_PROJECT - - :type project: string - :param project: Optional. The project name to use as default. - - :rtype: string or ``NoneType`` - :returns: Default project if it can be determined. - """ - if project is None: - project = _get_production_project() - - return project - - -def set_default_project(project=None): - """Set default project either explicitly or implicitly as fall-back. - - :type project: string - :param project: Optional. The project name to use as default. - - :raises: :class:`EnvironmentError` if no project was found. - """ - project = _determine_default_project(project=project) - if project is not None: - _DEFAULTS.project = project - else: - raise EnvironmentError('No project could be inferred.') - - class _DefaultsContainer(object): """Container for defaults. - :type project: string - :param project: Persistent implied project from environment. - :type bucket: :class:`gcloud.storage.bucket.Bucket` :param bucket: Persistent implied default bucket from environment. @@ -81,29 +29,11 @@ class _DefaultsContainer(object): :param connection: Persistent implied connection from environment. """ - @_lazy_property_deco - @staticmethod - def project(): - """Return the implicit default project.""" - return _determine_default_project() - - def __init__(self, project=None, bucket=None, connection=None, - implicit=False): - if project is not None or not implicit: - self.project = project + def __init__(self, bucket=None, connection=None): self.bucket = bucket self.connection = connection -def get_default_project(): - """Get default project. - - :rtype: string or ``NoneType`` - :returns: The default project if one has been set. - """ - return _DEFAULTS.project - - def get_default_bucket(): """Get default bucket. @@ -122,4 +52,4 @@ def get_default_connection(): return _DEFAULTS.connection -_DEFAULTS = _DefaultsContainer(implicit=True) +_DEFAULTS = _DefaultsContainer() diff --git a/gcloud/storage/api.py b/gcloud/storage/api.py index 268dc641b428..526a15e7be5f 100644 --- a/gcloud/storage/api.py +++ b/gcloud/storage/api.py @@ -19,8 +19,8 @@ """ from gcloud.exceptions import NotFound +from gcloud._helpers import get_default_project from gcloud.storage._implicit_environ import get_default_connection -from gcloud.storage._implicit_environ import get_default_project from gcloud.storage.bucket import Bucket from gcloud.storage.iterator import Iterator diff --git a/gcloud/storage/test__implicit_environ.py b/gcloud/storage/test__implicit_environ.py index b4529ce8596f..570a50f7d674 100644 --- a/gcloud/storage/test__implicit_environ.py +++ b/gcloud/storage/test__implicit_environ.py @@ -15,148 +15,21 @@ import unittest2 -class Test__get_production_project(unittest2.TestCase): +class Test_get_default_bucket(unittest2.TestCase): def _callFUT(self): - from gcloud.storage import _implicit_environ - return _implicit_environ._get_production_project() + from gcloud.storage._implicit_environ import get_default_bucket + return get_default_bucket() - def test_no_value(self): - import os - from gcloud._testing import _Monkey + def test_wo_override(self): + self.assertTrue(self._callFUT() is None) - environ = {} - with _Monkey(os, getenv=environ.get): - project = self._callFUT() - self.assertEqual(project, None) - def test_value_set(self): - import os - from gcloud._testing import _Monkey - from gcloud.storage._implicit_environ import _PROJECT_ENV_VAR_NAME +class Test_get_default_connection(unittest2.TestCase): - MOCK_PROJECT = object() - environ = {_PROJECT_ENV_VAR_NAME: MOCK_PROJECT} - with _Monkey(os, getenv=environ.get): - project = self._callFUT() - self.assertEqual(project, MOCK_PROJECT) - - -class Test__determine_default_project(unittest2.TestCase): - - def _callFUT(self, project=None): - from gcloud.storage._implicit_environ import _determine_default_project - return _determine_default_project(project=project) - - def _determine_default_helper(self, prod=None, project=None): - from gcloud._testing import _Monkey - from gcloud.storage import _implicit_environ - - _callers = [] - - def prod_mock(): - _callers.append('prod_mock') - return prod - - patched_methods = { - '_get_production_project': prod_mock, - } - - with _Monkey(_implicit_environ, **patched_methods): - returned_project = self._callFUT(project) - - return returned_project, _callers - - def test_no_value(self): - project, callers = self._determine_default_helper() - self.assertEqual(project, None) - self.assertEqual(callers, ['prod_mock']) - - def test_explicit(self): - PROJECT = object() - project, callers = self._determine_default_helper(project=PROJECT) - self.assertEqual(project, PROJECT) - self.assertEqual(callers, []) - - def test_prod(self): - PROJECT = object() - project, callers = self._determine_default_helper(prod=PROJECT) - self.assertEqual(project, PROJECT) - self.assertEqual(callers, ['prod_mock']) - - -class Test_set_default_project(unittest2.TestCase): - - def setUp(self): - from gcloud.storage._testing import _setup_defaults - _setup_defaults(self) - - def tearDown(self): - from gcloud.storage._testing import _tear_down_defaults - _tear_down_defaults(self) - - def _callFUT(self, project=None): - from gcloud.storage._implicit_environ import set_default_project - return set_default_project(project=project) - - def test_raises(self): - from gcloud._testing import _Monkey - from gcloud.storage import _implicit_environ - - _called_project = [] - - def mock_determine(project): - _called_project.append(project) - return None - - with _Monkey(_implicit_environ, - _determine_default_project=mock_determine): - self.assertRaises(EnvironmentError, self._callFUT) - - self.assertEqual(_called_project, [None]) - - def test_set_correctly(self): - from gcloud._testing import _Monkey - from gcloud.storage import _implicit_environ - - self.assertEqual(_implicit_environ._DEFAULTS.project, None) - - PROJECT = object() - _called_project = [] - - def mock_determine(project): - _called_project.append(project) - return PROJECT - - with _Monkey(_implicit_environ, - _determine_default_project=mock_determine): - self._callFUT() - - self.assertEqual(_implicit_environ._DEFAULTS.project, PROJECT) - self.assertEqual(_called_project, [None]) - - -class Test_lazy_loading(unittest2.TestCase): - - def setUp(self): - from gcloud.storage._testing import _setup_defaults - _setup_defaults(self, implicit=True) - - def tearDown(self): - from gcloud.storage._testing import _tear_down_defaults - _tear_down_defaults(self) - - def test_descriptor_for_project(self): - from gcloud._testing import _Monkey - from gcloud.storage import _implicit_environ - - self.assertFalse('project' in _implicit_environ._DEFAULTS.__dict__) - - DEFAULT = object() - - with _Monkey(_implicit_environ, - _determine_default_project=lambda: DEFAULT): - lazy_loaded = _implicit_environ._DEFAULTS.project + def _callFUT(self): + from gcloud.storage._implicit_environ import get_default_connection + return get_default_connection() - self.assertEqual(lazy_loaded, DEFAULT) - self.assertTrue('project' in _implicit_environ._DEFAULTS.__dict__) + def test_wo_override(self): + self.assertTrue(self._callFUT() is None) diff --git a/gcloud/storage/test_api.py b/gcloud/storage/test_api.py index 03ba6c610e00..44039b1e6f52 100644 --- a/gcloud/storage/test_api.py +++ b/gcloud/storage/test_api.py @@ -104,6 +104,7 @@ def test_empty(self): self.assertEqual(http._called_with['uri'], URI) def _get_all_buckets_non_empty_helper(self, project, use_default=False): + from gcloud._testing import _monkey_defaults as _base_monkey_defaults from gcloud.storage._testing import _monkey_defaults from gcloud.storage.connection import Connection BUCKET_NAME = 'bucket-name' @@ -120,8 +121,9 @@ def _get_all_buckets_non_empty_helper(self, project, use_default=False): ) if use_default: - with _monkey_defaults(project=project, connection=conn): - buckets = list(self._callFUT()) + with _base_monkey_defaults(project=project): + with _monkey_defaults(connection=conn): + buckets = list(self._callFUT()) else: buckets = list(self._callFUT(project, conn)) @@ -208,6 +210,7 @@ def _callFUT(self, bucket_name, project=None, connection=None): connection=connection) def _create_bucket_success_helper(self, project, use_default=False): + from gcloud._testing import _monkey_defaults as _base_monkey_defaults from gcloud.storage._testing import _monkey_defaults from gcloud.storage.connection import Connection from gcloud.storage.bucket import Bucket @@ -225,8 +228,9 @@ def _create_bucket_success_helper(self, project, use_default=False): ) if use_default: - with _monkey_defaults(project=project, connection=conn): - bucket = self._callFUT(BLOB_NAME) + with _base_monkey_defaults(project=project): + with _monkey_defaults(connection=conn): + bucket = self._callFUT(BLOB_NAME) else: bucket = self._callFUT(BLOB_NAME, project=project, connection=conn) diff --git a/gcloud/test__helpers.py b/gcloud/test__helpers.py index 4817a566699b..a543e3245f73 100644 --- a/gcloud/test__helpers.py +++ b/gcloud/test__helpers.py @@ -157,6 +157,151 @@ def test_socket_raises(self): self.assertEqual(dataset_id, None) +class Test__get_production_project(unittest2.TestCase): + + def _callFUT(self): + from gcloud._helpers import _get_production_project + return _get_production_project() + + def test_no_value(self): + import os + from gcloud._testing import _Monkey + + environ = {} + with _Monkey(os, getenv=environ.get): + project = self._callFUT() + self.assertEqual(project, None) + + def test_value_set(self): + import os + from gcloud._testing import _Monkey + from gcloud._helpers import _PROJECT_ENV_VAR_NAME + + MOCK_PROJECT = object() + environ = {_PROJECT_ENV_VAR_NAME: MOCK_PROJECT} + with _Monkey(os, getenv=environ.get): + project = self._callFUT() + self.assertEqual(project, MOCK_PROJECT) + + +class Test__determine_default_project(unittest2.TestCase): + + def _callFUT(self, project=None): + from gcloud._helpers import _determine_default_project + return _determine_default_project(project=project) + + def _determine_default_helper(self, prod=None, project=None): + from gcloud._testing import _Monkey + from gcloud import _helpers + + _callers = [] + + def prod_mock(): + _callers.append('prod_mock') + return prod + + patched_methods = { + '_get_production_project': prod_mock, + } + + with _Monkey(_helpers, **patched_methods): + returned_project = self._callFUT(project) + + return returned_project, _callers + + def test_no_value(self): + project, callers = self._determine_default_helper() + self.assertEqual(project, None) + self.assertEqual(callers, ['prod_mock']) + + def test_explicit(self): + PROJECT = object() + project, callers = self._determine_default_helper(project=PROJECT) + self.assertEqual(project, PROJECT) + self.assertEqual(callers, []) + + def test_prod(self): + PROJECT = object() + project, callers = self._determine_default_helper(prod=PROJECT) + self.assertEqual(project, PROJECT) + self.assertEqual(callers, ['prod_mock']) + + +class Test_set_default_project(unittest2.TestCase): + + def setUp(self): + from gcloud._testing import _setup_defaults + _setup_defaults(self) + + def tearDown(self): + from gcloud._testing import _tear_down_defaults + _tear_down_defaults(self) + + def _callFUT(self, project=None): + from gcloud._helpers import set_default_project + return set_default_project(project=project) + + def test_raises(self): + from gcloud._testing import _Monkey + from gcloud import _helpers + _called_project = [] + + def mock_determine(project): + _called_project.append(project) + return None + + with _Monkey(_helpers, _determine_default_project=mock_determine): + self.assertRaises(EnvironmentError, self._callFUT) + + self.assertEqual(_called_project, [None]) + + def test_set_correctly(self): + from gcloud._testing import _Monkey + from gcloud import _helpers + + self.assertEqual(_helpers._DEFAULTS.project, None) + + PROJECT = object() + _called_project = [] + + def mock_determine(project): + _called_project.append(project) + return PROJECT + + with _Monkey(_helpers, + _determine_default_project=mock_determine): + self._callFUT() + + self.assertEqual(_helpers._DEFAULTS.project, PROJECT) + self.assertEqual(_called_project, [None]) + + +class Test_lazy_loading(unittest2.TestCase): + + def setUp(self): + from gcloud._testing import _setup_defaults + _setup_defaults(self, implicit=True) + + def tearDown(self): + from gcloud._testing import _tear_down_defaults + _tear_down_defaults(self) + + def test_descriptor_for_project(self): + from gcloud._testing import _Monkey + from gcloud import _helpers + + self.assertFalse('project' in _helpers._DEFAULTS.__dict__) + + DEFAULT = object() + + with _Monkey(_helpers, + _determine_default_project=lambda: DEFAULT): + lazy_loaded = _helpers._DEFAULTS.project + + self.assertEqual(lazy_loaded, DEFAULT) + self.assertTrue('project' in _helpers._DEFAULTS.__dict__) + + class _AppIdentity(object): def __init__(self, app_id): diff --git a/regression/storage.py b/regression/storage.py index 85919c517f74..05e78f123be3 100644 --- a/regression/storage.py +++ b/regression/storage.py @@ -19,15 +19,15 @@ from gcloud import exceptions from gcloud import storage +from gcloud import _helpers from gcloud.storage._helpers import _base64_md5hash -from gcloud.storage import _implicit_environ from gcloud.storage.batch import Batch HTTP = httplib2.Http() SHARED_BUCKETS = {} -_implicit_environ._PROJECT_ENV_VAR_NAME = 'GCLOUD_TESTS_PROJECT_ID' +_helpers._PROJECT_ENV_VAR_NAME = 'GCLOUD_TESTS_PROJECT_ID' storage.set_defaults()