Repository: googleapis/google-cloud-python

Issue:
Remove __iter__ and __contains__ from Connection in storage
@tseaver WDYT?

This is part of a movement towards making `Connection` more of a background / low-level / raw interface (like it is for datastore).

What's more

``` python
for bucket in connection.get_all_buckets():
    ...
```

seems much more clear than

``` python
for bucket in connection:
    ...
```

We could potentially make up for `__contains__` by adding a `Bucket.exists` method / property.



Discussion:
Also related to removing `__contains__` is #588

@tseaver Gonna work on this.


Resolving patch:
diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py
--- a/gcloud/storage/bucket.py
+++ b/gcloud/storage/bucket.py
@@ -105,6 +105,18 @@ def __iter__(self):
     def __contains__(self, blob):
         return self.get_blob(blob) is not None
 
+    def exists(self):
+        """Determines whether or not this bucket exists.
+
+        :rtype: boolean
+        :returns: True if the bucket exists in Cloud Storage.
+        """
+        try:
+            self.connection.get_bucket(self.name)
+            return True
+        except NotFound:
+            return False
+
     @property
     def acl(self):
         """Create our ACL on demand."""
diff --git a/gcloud/storage/connection.py b/gcloud/storage/connection.py
--- a/gcloud/storage/connection.py
+++ b/gcloud/storage/connection.py
@@ -20,7 +20,6 @@
 
 from gcloud.connection import Connection as _Base
 from gcloud.exceptions import make_exception
-from gcloud.exceptions import NotFound
 from gcloud.storage.bucket import Bucket
 from gcloud.storage.iterator import Iterator
 
@@ -57,15 +56,9 @@ class Connection(_Base):
     A :class:`Connection` is actually iterable and will return the
     :class:`gcloud.storage.bucket.Bucket` objects inside the project::
 
-      >>> for bucket in connection:
+      >>> for bucket in connection.get_all_buckets():
       >>>   print bucket
       <Bucket: my-bucket-name>
-
-    In that same way, you can check for whether a bucket exists inside
-    the project using Python's ``in`` operator::
-
-      >>> print 'my-bucket-name' in connection
-      True
     """
 
     API_VERSION = 'v1'
@@ -82,16 +75,6 @@ def __init__(self, project, *args, **kwargs):
         super(Connection, self).__init__(*args, **kwargs)
         self.project = project
 
-    def __iter__(self):
-        return iter(_BucketIterator(connection=self))
-
-    def __contains__(self, bucket_name):
-        try:
-            self.get_bucket(bucket_name)
-            return True
-        except NotFound:
-            return False
-
     def build_api_url(self, path, query_params=None, api_base_url=None,
                       api_version=None, upload=False):
         """Construct an API url given a few components, some optional.
@@ -265,14 +248,11 @@ def get_all_buckets(self):
           >>> connection = storage.get_connection(project)
           >>> for bucket in connection.get_all_buckets():
           >>>   print bucket
-          >>> # ... is the same as ...
-          >>> for bucket in connection:
-          >>>   print bucket
 
         :rtype: list of :class:`gcloud.storage.bucket.Bucket` objects.
         :returns: All buckets belonging to this project.
         """
-        return list(self)
+        return iter(_BucketIterator(connection=self))
 
     def get_bucket(self, bucket_name):
         """Get a bucket by name.


Test patch:
diff --git a/gcloud/storage/test_bucket.py b/gcloud/storage/test_bucket.py
--- a/gcloud/storage/test_bucket.py
+++ b/gcloud/storage/test_bucket.py
@@ -151,6 +151,39 @@ def test___contains___hit(self):
         self.assertEqual(kw['method'], 'GET')
         self.assertEqual(kw['path'], '/b/%s/o/%s' % (NAME, BLOB_NAME))
 
+    def test_exists_miss(self):
+        from gcloud.exceptions import NotFound
+
+        class _FakeConnection(object):
+
+            _called_with = []
+
+            @classmethod
+            def get_bucket(cls, bucket_name):
+                cls._called_with.append(bucket_name)
+                raise NotFound(bucket_name)
+
+        NAME = 'name'
+        bucket = self._makeOne(connection=_FakeConnection, name=NAME)
+        self.assertFalse(bucket.exists())
+        self.assertEqual(_FakeConnection._called_with, [NAME])
+
+    def test_exists_hit(self):
+        class _FakeConnection(object):
+
+            _called_with = []
+
+            @classmethod
+            def get_bucket(cls, bucket_name):
+                cls._called_with.append(bucket_name)
+                # exists() does not use the return value
+                return object()
+
+        NAME = 'name'
+        bucket = self._makeOne(connection=_FakeConnection, name=NAME)
+        self.assertTrue(bucket.exists())
+        self.assertEqual(_FakeConnection._called_with, [NAME])
+
     def test_acl_property(self):
         from gcloud.storage.acl import BucketACL
         bucket = self._makeOne()
diff --git a/gcloud/storage/test_connection.py b/gcloud/storage/test_connection.py
--- a/gcloud/storage/test_connection.py
+++ b/gcloud/storage/test_connection.py
@@ -63,82 +63,6 @@ def authorize(self, http):
         self.assertTrue(conn.http is authorized)
         self.assertTrue(isinstance(creds._called_with, httplib2.Http))
 
-    def test___iter___empty(self):
-        PROJECT = 'project'
-        conn = self._makeOne(PROJECT)
-        URI = '/'.join([
-            conn.API_BASE_URL,
-            'storage',
-            conn.API_VERSION,
-            'b?project=%s' % PROJECT,
-        ])
-        http = conn._http = Http(
-            {'status': '200', 'content-type': 'application/json'},
-            '{}',
-        )
-        blobs = list(conn)
-        self.assertEqual(len(blobs), 0)
-        self.assertEqual(http._called_with['method'], 'GET')
-        self.assertEqual(http._called_with['uri'], URI)
-
-    def test___iter___non_empty(self):
-        PROJECT = 'project'
-        BLOB_NAME = 'blob-name'
-        conn = self._makeOne(PROJECT)
-        URI = '/'.join([
-            conn.API_BASE_URL,
-            'storage',
-            conn.API_VERSION,
-            'b?project=%s' % PROJECT,
-        ])
-        http = conn._http = Http(
-            {'status': '200', 'content-type': 'application/json'},
-            '{"items": [{"name": "%s"}]}' % BLOB_NAME,
-        )
-        blobs = list(conn)
-        self.assertEqual(len(blobs), 1)
-        self.assertEqual(blobs[0].name, BLOB_NAME)
-        self.assertEqual(http._called_with['method'], 'GET')
-        self.assertEqual(http._called_with['uri'], URI)
-
-    def test___contains___miss(self):
-        PROJECT = 'project'
-        NONESUCH = 'nonesuch'
-        conn = self._makeOne(PROJECT)
-        URI = '/'.join([
-            conn.API_BASE_URL,
-            'storage',
-            conn.API_VERSION,
-            'b',
-            'nonesuch?project=%s' % PROJECT,
-        ])
-        http = conn._http = Http(
-            {'status': '404', 'content-type': 'application/json'},
-            '{}',
-        )
-        self.assertFalse(NONESUCH in conn)
-        self.assertEqual(http._called_with['method'], 'GET')
-        self.assertEqual(http._called_with['uri'], URI)
-
-    def test___contains___hit(self):
-        PROJECT = 'project'
-        BLOB_NAME = 'blob-name'
-        conn = self._makeOne(PROJECT)
-        URI = '/'.join([
-            conn.API_BASE_URL,
-            'storage',
-            conn.API_VERSION,
-            'b',
-            '%s?project=%s' % (BLOB_NAME, PROJECT),
-        ])
-        http = conn._http = Http(
-            {'status': '200', 'content-type': 'application/json'},
-            '{"name": "%s"}' % BLOB_NAME,
-        )
-        self.assertTrue(BLOB_NAME in conn)
-        self.assertEqual(http._called_with['method'], 'GET')
-        self.assertEqual(http._called_with['uri'], URI)
-
     def test_build_api_url_no_extra_query_params(self):
         PROJECT = 'project'
         conn = self._makeOne(PROJECT)
@@ -370,14 +294,14 @@ def test_get_all_buckets_empty(self):
             {'status': '200', 'content-type': 'application/json'},
             '{}',
         )
-        blobs = conn.get_all_buckets()
-        self.assertEqual(len(blobs), 0)
+        buckets = list(conn.get_all_buckets())
+        self.assertEqual(len(buckets), 0)
         self.assertEqual(http._called_with['method'], 'GET')
         self.assertEqual(http._called_with['uri'], URI)
 
     def test_get_all_buckets_non_empty(self):
         PROJECT = 'project'
-        BLOB_NAME = 'blob-name'
+        BUCKET_NAME = 'bucket-name'
         conn = self._makeOne(PROJECT)
         URI = '/'.join([
             conn.API_BASE_URL,
@@ -387,11 +311,11 @@ def test_get_all_buckets_non_empty(self):
         ])
         http = conn._http = Http(
             {'status': '200', 'content-type': 'application/json'},
-            '{"items": [{"name": "%s"}]}' % BLOB_NAME,
+            '{"items": [{"name": "%s"}]}' % BUCKET_NAME,
         )
-        blobs = conn.get_all_buckets()
-        self.assertEqual(len(blobs), 1)
-        self.assertEqual(blobs[0].name, BLOB_NAME)
+        buckets = list(conn.get_all_buckets())
+        self.assertEqual(len(buckets), 1)
+        self.assertEqual(buckets[0].name, BUCKET_NAME)
         self.assertEqual(http._called_with['method'], 'GET')
         self.assertEqual(http._called_with['uri'], URI)