| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent b343acf commit d85d200
12 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,14 +44,34 @@ | |||
| 44 | 44 | which represents a lookup or search over the rows in the datastore. | |
| 45 | 45 | """ | |
| 46 | 46 | ||
| 47 | + import os | ||
| 48 | + | ||
| 47 | 49 | from gcloud import credentials | |
| 50 | + from gcloud.datastore import _implicit_environ | ||
| 48 | 51 | from gcloud.datastore.connection import Connection | |
| 49 | 52 | ||
| 50 | 53 | ||
| 51 | 54 | SCOPE = ('https://www.googleapis.com/auth/datastore ', | |
| 52 | 55 | 'https://www.googleapis.com/auth/userinfo.email') | |
| 53 | 56 | """The scope required for authenticating as a Cloud Datastore consumer.""" | |
| 54 | 57 | ||
| 58 | + _DATASET_ENV_VAR_NAME = 'GCLOUD_DATASET_ID' | ||
| 59 | + | ||
| 60 | + | ||
| 61 | + def _set_dataset_from_environ(): | ||
| 62 | + """Determines auth settings from local enviroment. | ||
| 63 | + | ||
| 64 | + Currently only supports enviroment variable but will implicitly | ||
| 65 | + support App Engine, Compute Engine and other environments in | ||
| 66 | + the future. | ||
| 67 | + | ||
| 68 | + Local environment variable used is: | ||
| 69 | + - GCLOUD_DATASET_ID | ||
| 70 | + """ | ||
| 71 | + local_dataset_id = os.getenv(_DATASET_ENV_VAR_NAME) | ||
| 72 | + if local_dataset_id is not None: | ||
| 73 | + _implicit_environ.DATASET = get_dataset(local_dataset_id) | ||
| 74 | + | ||
| 55 | 75 | ||
| 56 | 76 | def get_connection(): | |
| 57 | 77 | """Shortcut method to establish a connection to the Cloud Datastore. | |
@@ -97,3 +117,58 @@ def get_dataset(dataset_id): | |||
| 97 | 117 | """ | |
| 98 | 118 | connection = get_connection() | |
| 99 | 119 | return connection.dataset(dataset_id) | |
| 120 | + | ||
| 121 | + | ||
| 122 | + def _require_dataset(): | ||
| 123 | + """Convenience method to ensure DATASET is set. | ||
| 124 | + | ||
| 125 | + :rtype: :class:`gcloud.datastore.dataset.Dataset` | ||
| 126 | + :returns: A dataset based on the current environment. | ||
| 127 | + :raises: :class:`EnvironmentError` if DATASET is not set. | ||
| 128 | + """ | ||
| 129 | + if _implicit_environ.DATASET is None: | ||
| 130 | + raise EnvironmentError('Dataset could not be implied.') | ||
| 131 | + return _implicit_environ.DATASET | ||
| 132 | + | ||
| 133 | + | ||
| 134 | + def get_entity(key): | ||
| 135 | + """Retrieves entity from implicit dataset, along with its attributes. | ||
| 136 | + | ||
| 137 | + :type key: :class:`gcloud.datastore.key.Key` | ||
| 138 | + :param key: The name of the item to retrieve. | ||
| 139 | + | ||
| 140 | + :rtype: :class:`gcloud.datastore.entity.Entity` or ``None`` | ||
| 141 | + :return: The requested entity, or ``None`` if there was no match found. | ||
| 142 | + """ | ||
| 143 | + return _require_dataset().get_entity(key) | ||
| 144 | + | ||
| 145 | + | ||
| 146 | + def get_entities(keys): | ||
| 147 | + """Retrieves entities from implied dataset, along with their attributes. | ||
| 148 | + | ||
| 149 | + :type keys: list of :class:`gcloud.datastore.key.Key` | ||
| 150 | + :param keys: The name of the item to retrieve. | ||
| 151 | + | ||
| 152 | + :rtype: list of :class:`gcloud.datastore.entity.Entity` | ||
| 153 | + :return: The requested entities. | ||
| 154 | + """ | ||
| 155 | + return _require_dataset().get_entities(keys) | ||
| 156 | + | ||
| 157 | + | ||
| 158 | + def allocate_ids(incomplete_key, num_ids): | ||
| 159 | + """Allocates a list of IDs from a partial key. | ||
| 160 | + | ||
| 161 | + :type incomplete_key: A :class:`gcloud.datastore.key.Key` | ||
| 162 | + :param incomplete_key: The partial key to use as base for allocated IDs. | ||
| 163 | + | ||
| 164 | + :type num_ids: A :class:`int`. | ||
| 165 | + :param num_ids: The number of IDs to allocate. | ||
| 166 | + | ||
| 167 | + :rtype: list of :class:`gcloud.datastore.key.Key` | ||
| 168 | + :return: The (complete) keys allocated with `incomplete_key` as root. | ||
| 169 | + """ | ||
| 170 | + return _require_dataset().allocate_ids(incomplete_key, num_ids) | ||
| 171 | + | ||
| 172 | + | ||
| 173 | + # Set DATASET if it can be implied from the environment. | ||
| 174 | + _set_dataset_from_environ() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,24 @@ | |||
| 1 | + """Module to provide implicit behavior based on enviroment. | ||
| 2 | + | ||
| 3 | + Acts as a mutable namespace to allow the datastore package to | ||
| 4 | + imply the current dataset from the enviroment. | ||
| 5 | + | ||
| 6 | + Also provides a base class for classes in the `datastore` package | ||
| 7 | + which could utilize the implicit enviroment. | ||
| 8 | + """ | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + DATASET = None | ||
| 12 | + """Module global to allow persistent implied dataset from enviroment.""" | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + class _DatastoreBase(object): | ||
| 16 | + """Base for all classes in the datastore package. | ||
| 17 | + | ||
| 18 | + Uses the implicit DATASET object as a default dataset attached | ||
| 19 | + to the instances being created. Stores the dataset passed in | ||
| 20 | + on the protected (i.e. non-public) attribute `_dataset`. | ||
| 21 | + """ | ||
| 22 | + | ||
| 23 | + def __init__(self, dataset=None): | ||
| 24 | + self._dataset = dataset or DATASET | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,6 +14,7 @@ | |||
| 14 | 14 | ||
| 15 | 15 | """Class for representing a single entity in the Cloud Datastore.""" | |
| 16 | 16 | ||
| 17 | + from gcloud.datastore import _implicit_environ | ||
| 17 | 18 | from gcloud.datastore import datastore_v1_pb2 as datastore_pb | |
| 18 | 19 | from gcloud.datastore.key import Key | |
| 19 | 20 | ||
@@ -95,7 +96,9 @@ class Entity(dict): | |||
| 95 | 96 | ||
| 96 | 97 | def __init__(self, dataset=None, kind=None, exclude_from_indexes=()): | |
| 97 | 98 | super(Entity, self).__init__() | |
| 98 | - self._dataset = dataset | ||
| 99 | + # Does not inherit from object, so we don't use | ||
| 100 | + # _implicit_environ._DatastoreBase to avoid split MRO. | ||
| 101 | + self._dataset = dataset or _implicit_environ.DATASET | ||
| 99 | 102 | if kind: | |
| 100 | 103 | self._key = Key().kind(kind) | |
| 101 | 104 | else: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,12 +16,13 @@ | |||
| 16 | 16 | ||
| 17 | 17 | import base64 | |
| 18 | 18 | ||
| 19 | + from gcloud.datastore import _implicit_environ | ||
| 19 | 20 | from gcloud.datastore import datastore_v1_pb2 as datastore_pb | |
| 20 | 21 | from gcloud.datastore import helpers | |
| 21 | 22 | from gcloud.datastore.key import Key | |
| 22 | 23 | ||
| 23 | 24 | ||
| 24 | - class Query(object): | ||
| 25 | + class Query(_implicit_environ._DatastoreBase): | ||
| 25 | 26 | """A Query against the Cloud Datastore. | |
| 26 | 27 | ||
| 27 | 28 | This class serves as an abstraction for creating a query over data | |
@@ -71,7 +72,7 @@ class Query(object): | |||
| 71 | 72 | """Mapping of operator strings and their protobuf equivalents.""" | |
| 72 | 73 | ||
| 73 | 74 | def __init__(self, kind=None, dataset=None, namespace=None): | |
| 74 | - self._dataset = dataset | ||
| 75 | + super(Query, self).__init__(dataset=dataset) | ||
| 75 | 76 | self._namespace = namespace | |
| 76 | 77 | self._pb = datastore_pb.Query() | |
| 77 | 78 | self._offset = 0 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,6 +35,47 @@ def test_it(self): | |||
| 35 | 35 | self.assertTrue(client._get_app_default_called) | |
| 36 | 36 | ||
| 37 | 37 | ||
| 38 | + class Test__set_dataset_from_environ(unittest2.TestCase): | ||
| 39 | + | ||
| 40 | + def _callFUT(self): | ||
| 41 | + from gcloud.datastore import _set_dataset_from_environ | ||
| 42 | + return _set_dataset_from_environ() | ||
| 43 | + | ||
| 44 | + def _test_with_environ(self, environ, expected_result): | ||
| 45 | + import os | ||
| 46 | + from gcloud._testing import _Monkey | ||
| 47 | + from gcloud import datastore | ||
| 48 | + from gcloud.datastore import _implicit_environ | ||
| 49 | + | ||
| 50 | + # Check the environment is unset. | ||
| 51 | + self.assertEqual(_implicit_environ.DATASET, None) | ||
| 52 | + | ||
| 53 | + def custom_getenv(key): | ||
| 54 | + return environ.get(key) | ||
| 55 | + | ||
| 56 | + def custom_get_dataset(dataset_id): | ||
| 57 | + return dataset_id | ||
| 58 | + | ||
| 59 | + with _Monkey(os, getenv=custom_getenv): | ||
| 60 | + with _Monkey(datastore, get_dataset=custom_get_dataset): | ||
| 61 | + self._callFUT() | ||
| 62 | + | ||
| 63 | + self.assertEqual(_implicit_environ.DATASET, expected_result) | ||
| 64 | + | ||
| 65 | + def test_set_from_env_var(self): | ||
| 66 | + from gcloud.datastore import _DATASET_ENV_VAR_NAME | ||
| 67 | + | ||
| 68 | + # Make a custom getenv function to Monkey. | ||
| 69 | + DATASET = 'dataset' | ||
| 70 | + VALUES = { | ||
| 71 | + _DATASET_ENV_VAR_NAME: DATASET, | ||
| 72 | + } | ||
| 73 | + self._test_with_environ(VALUES, DATASET) | ||
| 74 | + | ||
| 75 | + def test_no_env_var_set(self): | ||
| 76 | + self._test_with_environ({}, None) | ||
| 77 | + | ||
| 78 | + | ||
| 38 | 79 | class Test_get_dataset(unittest2.TestCase): | |
| 39 | 80 | ||
| 40 | 81 | def _callFUT(self, dataset_id): | |
@@ -56,3 +97,104 @@ def test_it(self): | |||
| 56 | 97 | self.assertTrue(isinstance(found.connection(), Connection)) | |
| 57 | 98 | self.assertEqual(found.id(), DATASET_ID) | |
| 58 | 99 | self.assertTrue(client._get_app_default_called) | |
| 100 | + | ||
| 101 | + | ||
| 102 | + class Test_implicit_behavior(unittest2.TestCase): | ||
| 103 | + | ||
| 104 | + def test__require_dataset(self): | ||
| 105 | + import gcloud.datastore | ||
| 106 | + from gcloud.datastore import _implicit_environ | ||
| 107 | + original_dataset = _implicit_environ.DATASET | ||
| 108 | + | ||
| 109 | + try: | ||
| 110 | + _implicit_environ.DATASET = None | ||
| 111 | + self.assertRaises(EnvironmentError, | ||
| 112 | + gcloud.datastore._require_dataset) | ||
| 113 | + NEW_DATASET = object() | ||
| 114 | + _implicit_environ.DATASET = NEW_DATASET | ||
| 115 | + self.assertEqual(gcloud.datastore._require_dataset(), NEW_DATASET) | ||
| 116 | + finally: | ||
| 117 | + _implicit_environ.DATASET = original_dataset | ||
| 118 | + | ||
| 119 | + def test_get_entity(self): | ||
| 120 | + import gcloud.datastore | ||
| 121 | + from gcloud.datastore import _implicit_environ | ||
| 122 | + from gcloud.datastore.test_entity import _Dataset | ||
| 123 | + from gcloud._testing import _Monkey | ||
| 124 | + | ||
| 125 | + CUSTOM_DATASET = _Dataset() | ||
| 126 | + DUMMY_KEY = object() | ||
| 127 | + DUMMY_VAL = object() | ||
| 128 | + CUSTOM_DATASET[DUMMY_KEY] = DUMMY_VAL | ||
| 129 | + with _Monkey(_implicit_environ, DATASET=CUSTOM_DATASET): | ||
| 130 | + result = gcloud.datastore.get_entity(DUMMY_KEY) | ||
| 131 | + self.assertTrue(result is DUMMY_VAL) | ||
| 132 | + | ||
| 133 | + def test_get_entities(self): | ||
| 134 | + import gcloud.datastore | ||
| 135 | + from gcloud.datastore import _implicit_environ | ||
| 136 | + from gcloud.datastore.test_entity import _Dataset | ||
| 137 | + from gcloud._testing import _Monkey | ||
| 138 | + | ||
| 139 | + CUSTOM_DATASET = _Dataset() | ||
| 140 | + DUMMY_KEYS = [object(), object()] | ||
| 141 | + DUMMY_VALS = [object(), object()] | ||
| 142 | + for key, val in zip(DUMMY_KEYS, DUMMY_VALS): | ||
| 143 | + CUSTOM_DATASET[key] = val | ||
| 144 | + | ||
| 145 | + with _Monkey(_implicit_environ, DATASET=CUSTOM_DATASET): | ||
| 146 | + result = gcloud.datastore.get_entities(DUMMY_KEYS) | ||
| 147 | + self.assertTrue(result == DUMMY_VALS) | ||
| 148 | + | ||
| 149 | + def test_allocate_ids(self): | ||
| 150 | + import gcloud.datastore | ||
| 151 | + from gcloud.datastore import _implicit_environ | ||
| 152 | + from gcloud.datastore.key import Key | ||
| 153 | + from gcloud.datastore.test_entity import _Dataset | ||
| 154 | + from gcloud._testing import _Monkey | ||
| 155 | + | ||
| 156 | + CUSTOM_DATASET = _Dataset() | ||
| 157 | + INCOMPLETE_KEY = Key() | ||
| 158 | + NUM_IDS = 2 | ||
| 159 | + with _Monkey(_implicit_environ, DATASET=CUSTOM_DATASET): | ||
| 160 | + result = gcloud.datastore.allocate_ids(INCOMPLETE_KEY, NUM_IDS) | ||
| 161 | + | ||
| 162 | + # Check the IDs returned. | ||
| 163 | + self.assertEqual([key.id() for key in result], range(1, NUM_IDS + 1)) | ||
| 164 | + | ||
| 165 | + def test_set_DATASET(self): | ||
| 166 | + import os | ||
| 167 | + from gcloud._testing import _Monkey | ||
| 168 | + from gcloud.test_credentials import _Client | ||
| 169 | + from gcloud import credentials | ||
| 170 | + from gcloud.datastore import _implicit_environ | ||
| 171 | + | ||
| 172 | + # Make custom client for doing auth. Have to fake auth since we | ||
| 173 | + # can't monkey patch `datastore.get_dataset` while reloading the | ||
| 174 | + # `datastore.__init__` module. | ||
| 175 | + client = _Client() | ||
| 176 | + | ||
| 177 | + # Fake auth variables. | ||
| 178 | + DATASET = 'dataset' | ||
| 179 | + | ||
| 180 | + # Make a custom getenv function to Monkey. | ||
| 181 | + VALUES = { | ||
| 182 | + 'GCLOUD_DATASET_ID': DATASET, | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + def custom_getenv(key): | ||
| 186 | + return VALUES.get(key) | ||
| 187 | + | ||
| 188 | + # Perform the import again with our test patches. | ||
| 189 | + with _Monkey(credentials, client=client): | ||
| 190 | + with _Monkey(os, getenv=custom_getenv): | ||
| 191 | + import gcloud.datastore | ||
| 192 | + reload(gcloud.datastore) | ||
| 193 | + | ||
| 194 | + # Check that the DATASET was correctly implied from the environ. | ||
| 195 | + implicit_dataset = _implicit_environ.DATASET | ||
| 196 | + self.assertEqual(implicit_dataset.id(), DATASET) | ||
| 197 | + # Check that the credentials on the implicit DATASET was set on the | ||
| 198 | + # fake client. | ||
| 199 | + cnxn_credentials = implicit_dataset.connection().credentials | ||
| 200 | + self.assertTrue(cnxn_credentials is client._signed) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -227,7 +227,7 @@ def test_allocate_ids(self): | |||
| 227 | 227 | DATASET = self._makeOne(DATASET_ID, connection=CONNECTION) | |
| 228 | 228 | result = DATASET.allocate_ids(INCOMPLETE_KEY, NUM_IDS) | |
| 229 | 229 | ||
| 230 | - # Check the IDs returned match _PathElementProto. | ||
| 230 | + # Check the IDs returned match. | ||
| 231 | 231 | self.assertEqual([key._id for key in result], range(NUM_IDS)) | |
| 232 | 232 | ||
| 233 | 233 | # Check connection is called correctly. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,8 +23,10 @@ | |||
| 23 | 23 | class TestEntity(unittest2.TestCase): | |
| 24 | 24 | ||
| 25 | 25 | def _getTargetClass(self): | |
| 26 | + from gcloud.datastore import _implicit_environ | ||
| 26 | 27 | from gcloud.datastore.entity import Entity | |
| 27 | 28 | ||
| 29 | + _implicit_environ.DATASET = None | ||
| 28 | 30 | return Entity | |
| 29 | 31 | ||
| 30 | 32 | def _makeOne(self, dataset=_MARKER, kind=_KIND, exclude_from_indexes=()): | |
@@ -265,6 +267,13 @@ def __init__(self, connection=None): | |||
| 265 | 267 | super(_Dataset, self).__init__() | |
| 266 | 268 | self._connection = connection | |
| 267 | 269 | ||
| 270 | + def __bool__(self): | ||
| 271 | + # Make sure the objects are Truth-y since an empty | ||
| 272 | + # dict with _connection set will still be False-y. | ||
| 273 | + return True | ||
| 274 | + | ||
| 275 | + __nonzero__ = __bool__ | ||
| 276 | + | ||
| 268 | 277 | def id(self): | |
| 269 | 278 | return _DATASET_ID | |
| 270 | 279 | ||
@@ -274,6 +283,12 @@ def connection(self): | |||
| 274 | 283 | def get_entity(self, key): | |
| 275 | 284 | return self.get(key) | |
| 276 | 285 | ||
| 286 | + def get_entities(self, keys): | ||
| 287 | + return [self.get(key) for key in keys] | ||
| 288 | + | ||
| 289 | + def allocate_ids(self, incomplete_key, num_ids): | ||
| 290 | + return [incomplete_key.id(i + 1) for i in range(num_ids)] | ||
| 291 | + | ||
| 277 | 292 | ||
| 278 | 293 | class _Connection(object): | |
| 279 | 294 | _transaction = _saved = _deleted = None | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,8 +20,11 @@ class Test_entity_from_protobuf(unittest2.TestCase): | |||
| 20 | 20 | _MARKER = object() | |
| 21 | 21 | ||
| 22 | 22 | def _callFUT(self, val, dataset=_MARKER): | |
| 23 | + from gcloud.datastore import _implicit_environ | ||
| 23 | 24 | from gcloud.datastore.helpers import entity_from_protobuf | |
| 24 | 25 | ||
| 26 | + _implicit_environ.DATASET = None | ||
| 27 | + | ||
| 25 | 28 | if dataset is self._MARKER: | |
| 26 | 29 | return entity_from_protobuf(val) | |
| 27 | 30 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,8 +18,10 @@ | |||
| 18 | 18 | class TestQuery(unittest2.TestCase): | |
| 19 | 19 | ||
| 20 | 20 | def _getTargetClass(self): | |
| 21 | + from gcloud.datastore import _implicit_environ | ||
| 21 | 22 | from gcloud.datastore.query import Query | |
| 22 | 23 | ||
| 24 | + _implicit_environ.DATASET = None | ||
| 23 | 25 | return Query | |
| 24 | 26 | ||
| 25 | 27 | def _makeOne(self, kind=None, dataset=None, namespace=None): | |
| Back | FazBrowse Home | New Git URL |
0 commit comments