diff --git a/gcloud/_localstack.py b/gcloud/_localstack.py new file mode 100644 index 000000000000..2026acbd4fc1 --- /dev/null +++ b/gcloud/_localstack.py @@ -0,0 +1,61 @@ +# Copyright 2014 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Thread-local resource stack. + +This module is not part of the public API surface of `gcloud`. +""" + +try: + from threading import local as Local +except ImportError: # pragma: NO COVER (who doesn't have it?) + class Local(object): + """Placeholder for non-threaded applications.""" + + +class _LocalStack(Local): + """Manage a thread-local LIFO stack of resources. + + Intended for use in :class:`gcloud.datastore.batch.Batch.__enter__`, + :class:`gcloud.storage.batch.Batch.__enter__`, etc. + """ + def __init__(self): + super(_LocalStack, self).__init__() + self._stack = [] + + def __iter__(self): + """Iterate the stack in LIFO order. + """ + return iter(reversed(self._stack)) + + def push(self, resource): + """Push a resource onto our stack. + """ + self._stack.append(resource) + + def pop(self): + """Pop a resource from our stack. + + :raises: IndexError if the stack is empty. + :returns: the top-most resource, after removing it. + """ + return self._stack.pop() + + @property + def top(self): + """Get the top-most resource + + :returns: the top-most item, or None if the stack is empty. + """ + if len(self._stack) > 0: + return self._stack[-1] diff --git a/gcloud/datastore/batch.py b/gcloud/datastore/batch.py index c489934087ba..2b61a96ab820 100644 --- a/gcloud/datastore/batch.py +++ b/gcloud/datastore/batch.py @@ -13,66 +13,15 @@ # limitations under the License. """Create / interact with a batch of updates / deletes.""" -try: - from threading import local as Local -except ImportError: # pragma: NO COVER (who doesn't have it?) - class Local(object): - """Placeholder for non-threaded applications.""" +from gcloud._localstack import _LocalStack from gcloud.datastore import _implicit_environ from gcloud.datastore import helpers from gcloud.datastore.key import _dataset_ids_equal from gcloud.datastore import _datastore_v1_pb2 as datastore_pb -class _Batches(Local): - """Manage a thread-local LIFO stack of active batches / transactions. - - Intended for use only in :class:`gcloud.datastore.batch.Batch.__enter__` - """ - def __init__(self): - super(_Batches, self).__init__() - self._stack = [] - - def __iter__(self): - """Iterate the stack in LIFO order. - """ - return iter(reversed(self._stack)) - - def push(self, batch): - """Push a batch / transaction onto our stack. - - Intended for use only in :meth:`gcloud.datastore.batch.Batch.__enter__` - - :type batch: :class:`gcloud.datastore.batch.Batch` or - :class:`gcloud.datastore.transaction.Transaction` - """ - self._stack.append(batch) - - def pop(self): - """Pop a batch / transaction from our stack. - - Intended for use only in :meth:`gcloud.datastore.batch.Batch.__enter__` - - :rtype: :class:`gcloud.datastore.batch.Batch` or - :class:`gcloud.datastore.transaction.Transaction` - :raises: IndexError if the stack is empty. - """ - return self._stack.pop() - - @property - def top(self): - """Get the top-most batch / transaction - - :rtype: :class:`gcloud.datastore.batch.Batch` or - :class:`gcloud.datastore.transaction.Transaction` or None - :returns: the top-most item, or None if the stack is empty. - """ - if len(self._stack) > 0: - return self._stack[-1] - - -_BATCHES = _Batches() +_BATCHES = _LocalStack() class Batch(object): diff --git a/gcloud/datastore/test_batch.py b/gcloud/datastore/test_batch.py index 579fee92a612..9f3a1b70dfb8 100644 --- a/gcloud/datastore/test_batch.py +++ b/gcloud/datastore/test_batch.py @@ -15,34 +15,6 @@ import unittest2 -class Test_Batches(unittest2.TestCase): - - def _getTargetClass(self): - from gcloud.datastore.batch import _Batches - - return _Batches - - def _makeOne(self): - return self._getTargetClass()() - - def test_it(self): - batch1, batch2 = object(), object() - batches = self._makeOne() - self.assertEqual(list(batches), []) - self.assertTrue(batches.top is None) - batches.push(batch1) - self.assertTrue(batches.top is batch1) - batches.push(batch2) - self.assertTrue(batches.top is batch2) - popped = batches.pop() - self.assertTrue(popped is batch2) - self.assertTrue(batches.top is batch1) - self.assertEqual(list(batches), [batch1]) - popped = batches.pop() - self.assertTrue(batches.top is None) - self.assertEqual(list(batches), []) - - class TestBatch(unittest2.TestCase): def _getTargetClass(self): diff --git a/gcloud/test__localstack.py b/gcloud/test__localstack.py new file mode 100644 index 000000000000..f615b8caa790 --- /dev/null +++ b/gcloud/test__localstack.py @@ -0,0 +1,43 @@ +# Copyright 2014 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest2 + + +class Test__LocalStack(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud._localstack import _LocalStack + + return _LocalStack + + def _makeOne(self): + return self._getTargetClass()() + + def test_it(self): + batch1, batch2 = object(), object() + batches = self._makeOne() + self.assertEqual(list(batches), []) + self.assertTrue(batches.top is None) + batches.push(batch1) + self.assertTrue(batches.top is batch1) + batches.push(batch2) + self.assertTrue(batches.top is batch2) + popped = batches.pop() + self.assertTrue(popped is batch2) + self.assertTrue(batches.top is batch1) + self.assertEqual(list(batches), [batch1]) + popped = batches.pop() + self.assertTrue(batches.top is None) + self.assertEqual(list(batches), [])