diff --git a/gcloud/datastore/key.py b/gcloud/datastore/key.py index 7f06daa0a416..4edc07b38828 100644 --- a/gcloud/datastore/key.py +++ b/gcloud/datastore/key.py @@ -11,6 +11,18 @@ class Key(object): """ def __init__(self, dataset=None, namespace=None, path=None): + """Constructor / initializer for a key. + + :type dataset: :class:`gcloud.datastore.dataset.Dataset` + :param dataset: A dataset instance for the key. + + :type namespace: :class:`str` + :param namespace: A namespace identifier for the key. + + :type path: sequence of dicts + :param path: Each dict must have keys 'kind' (a string) and optionally + 'name' (a string) or 'id' (an integer). + """ self._dataset = dataset self._namespace = namespace self._path = path or [{'kind': ''}] @@ -21,6 +33,9 @@ def _clone(self): We make a shallow copy of the :class:`gcloud.datastore.dataset.Dataset` because it holds a reference an authenticated connection, which we don't want to lose. + + :rtype: :class:`gcloud.datastore.key.Key` + :returns: a new `Key` instance """ clone = copy.deepcopy(self) clone._dataset = self._dataset # Make a shallow copy of the Dataset. @@ -28,6 +43,20 @@ def _clone(self): @classmethod def from_protobuf(cls, pb, dataset=None): + """Factory method for creating a key based on a protobuf. + + The protobuf should be one returned from the Cloud Datastore Protobuf API. + + :type pb: :class:`gcloud.datastore.datastore_v1_pb2.Key` + :param pb: The Protobuf representing the key. + + :type dataset: :class:`gcloud.datastore.dataset.Dataset` + :param dataset: A dataset instance. If not passed, defaults to an + instance whose ID is derived from pb. + + :rtype: :class:`gcloud.datastore.key.Key` + :returns: a new `Key` instance + """ path = [] for element in pb.path_element: element_dict = {'kind': element.kind} @@ -42,10 +71,18 @@ def from_protobuf(cls, pb, dataset=None): if not dataset: dataset = Dataset(id=pb.partition_id.dataset_id) + namespace = pb.partition_id.namespace + else: + namespace = None - return cls(path=path, dataset=dataset) + return cls(dataset, namespace, path) def to_protobuf(self): + """Return a protobuf corresponding to the key. + + :rtype: :class:`gcloud.datastore.datastore_v1_pb2.Key` + :returns: The Protobuf representing the key. + """ key = datastore_pb.Key() # Technically a dataset is required to do anything with the key, @@ -77,6 +114,23 @@ def to_protobuf(self): @classmethod def from_path(cls, *args, **kwargs): + """Factory method for creating a key based on a path. + + :type args: :class:`tuple + :param args: sequence of even length, where the first of each + pair is a string representing the 'kind' of the path element, and the + second of the pair is either a string (for the path element's name) + or an integer (for its id). + + :type kwargs: :class:`dict` + :param kwargs: Other named parameters which can be passed to `__init__()`. + + :rtype: :class:`gcloud.datastore.key.Key` + :returns: a new `Key` instance + """ + if len(args) % 2: + raise ValueError('Must pass an even number of args.') + path = [] items = iter(args) @@ -92,9 +146,25 @@ def from_path(cls, *args, **kwargs): return cls(**kwargs) def is_partial(self): + """Boolean test: is the key fully mapped onto a backend entity? + + :rtype: :class:`bool` + :returns: True if the last element of the key's path does not have an 'id' + or a 'name'. + """ return (self.id_or_name() is None) def dataset(self, dataset=None): + """Setter / getter. + + :type dataset: :class:`gcloud.datastore.dataset.Dataset` + :param dataset: A dataset instance for the key. + + :rtype: :class:`Key` (for setter); or + :class:`gcloud.datastore.dataset.Dataset` (for getter) + :returns: a new key, cloned from self., with the given dataset (setter); + or self's dataset (getter). + """ if dataset: clone = self._clone() clone._dataset = dataset @@ -103,6 +173,15 @@ def dataset(self, dataset=None): return self._dataset def namespace(self, namespace=None): + """Setter / getter. + + :type namespace: :class:`str` + :param namespace: A namespace identifier for the key. + + :rtype: :class:`Key` (for setter); or :class:`str` (for getter) + :returns: a new key, cloned from self., with the given namespace (setter); + or self's namespace (getter). + """ if namespace: clone = self._clone() clone._namespace = namespace @@ -111,6 +190,16 @@ def namespace(self, namespace=None): return self._namespace def path(self, path=None): + """Setter / getter. + + :type path: sequence of dicts + :param path: Each dict must have keys 'kind' (a string) and optionally + 'name' (a string) or 'id' (an integer). + + :rtype: :class:`Key` (for setter); or :class:`str` (for getter) + :returns: a new key, cloned from self., with the given path (setter); + or self's path (getter). + """ if path: clone = self._clone() clone._path = path @@ -119,6 +208,15 @@ def path(self, path=None): return self._path def kind(self, kind=None): + """Setter / getter. Based on the last element of path. + + :type kind: :class:`str` + :param kind: The new kind for the key. + + :rtype: :class:`Key` (for setter); or :class:`str` (for getter) + :returns: a new key, cloned from self., with the given kind (setter); + or self's kind (getter). + """ if kind: clone = self._clone() clone._path[-1]['kind'] = kind @@ -127,6 +225,15 @@ def kind(self, kind=None): return self._path[-1]['kind'] def id(self, id=None): + """Setter / getter. Based on the last element of path. + + :type kind: :class:`str` + :param kind: The new kind for the key. + + :rtype: :class:`Key` (for setter); or :class:`int` (for getter) + :returns: a new key, cloned from self., with the given id (setter); + or self's id (getter). + """ if id: clone = self._clone() clone._path[-1]['id'] = id @@ -135,6 +242,15 @@ def id(self, id=None): return self._path[-1].get('id') def name(self, name=None): + """Setter / getter. Based on the last element of path. + + :type kind: :class:`str` + :param kind: The new name for the key. + + :rtype: :class:`Key` (for setter); or :class:`str` (for getter) + :returns: a new key, cloned from self., with the given name (setter); + or self's name (getter). + """ if name: clone = self._clone() clone._path[-1]['name'] = name @@ -143,11 +259,24 @@ def name(self, name=None): return self._path[-1].get('name') def id_or_name(self): + """Getter. Based on the last element of path. + + :rtype: :class:`int` (if 'id' is set); or :class:`str` (the 'name') + :returns: True if the last element of the key's path has either an 'id' + or a 'name'. + """ return self.id() or self.name() def parent(self):#pragma NO COVER - # See https://github.com/GoogleCloudPlatform/gcloud-python/issues/135 - raise NotImplementedError + """Getter: return a new key for the next highest element in path. + + :rtype: :class:`gcloud.datastore.key.Key` + :returns: a new `Key` instance, whose path consists of all but the last + element of self's path. If self has only one path element, return None. + """ + if len(self._path) <= 1: + return None + return self.path(self.path()[:-1]) def __repr__(self): #pragma NO COVER return '' % self.path() diff --git a/gcloud/datastore/test_key.py b/gcloud/datastore/test_key.py index 5da0f8af19cf..0b7a441a68cc 100644 --- a/gcloud/datastore/test_key.py +++ b/gcloud/datastore/test_key.py @@ -80,12 +80,20 @@ def test_from_protobuf_w_dataset_in_pb(self): key = self._getTargetClass().from_protobuf(pb) self.assertEqual(key.dataset().id(), _DATASET) - def test_from_protobuf_w_namespace_in_pb(self): + def test_from_protobuf_w_namespace_in_pb_wo_dataset_passed(self): _NAMESPACE = 'NAMESPACE' pb = self._makePB(namespace=_NAMESPACE) key = self._getTargetClass().from_protobuf(pb) - # See: https://github.com/GoogleCloudPlatform/gcloud-python/issues/133 - # self.assertEqual(key.namespace(), _NAMESPACE) XXX + self.assertEqual(key.namespace(), _NAMESPACE) + + def test_from_protobuf_w_namespace_in_pb_w_dataset_passed(self): + from gcloud.datastore.dataset import Dataset + _DATASET = 'DATASET' + _NAMESPACE = 'NAMESPACE' + dataset = Dataset(_DATASET) + pb = self._makePB(namespace=_NAMESPACE) + key = self._getTargetClass().from_protobuf(pb, dataset) + self.assertEqual(key.namespace(), None) def test_from_protobuf_w_path_in_pb(self): _DATASET = 'DATASET' @@ -164,12 +172,11 @@ def test_from_path_empty(self): self.assertEqual(key.path(), [{'kind': ''}]) def test_from_path_single_element(self): - # See https://github.com/GoogleCloudPlatform/gcloud-python/issues/134 - key = self._getTargetClass().from_path('abc') - self.assertEqual(key.dataset(), None) - self.assertEqual(key.namespace(), None) - self.assertEqual(key.kind(), '') # XXX s.b. 'abc'? - self.assertEqual(key.path(), [{'kind': ''}]) # XXX s.b. 'abc'? + self.assertRaises(ValueError, self._getTargetClass().from_path, 'abc') + + def test_from_path_three_elements(self): + self.assertRaises(ValueError, self._getTargetClass().from_path, + 'abc', 'def', 'ghi') def test_from_path_two_elements_second_string(self): key = self._getTargetClass().from_path('abc', 'def') @@ -339,6 +346,14 @@ def test_id_or_name_w_name_only(self): key = self._makeOne(path=_PATH) self.assertEqual(key.id_or_name(), _NAME) - def _ugh(self): - protokey = key.to_protobuf() - self.assertEqual(protokey.partition_id.dataset_id, _DATASET) + def test_parent_default(self): + key = self._makeOne() + self.assertEqual(key.parent(), None) + + def test_parent_explicit_top_level(self): + key = self._getTargetClass().from_path('abc', 'def') + self.assertEqual(key.parent(), None) + + def test_parent_explicit_nested(self): + key = self._getTargetClass().from_path('abc', 'def', 'ghi', 123) + self.assertEqual(key.parent().path(), [{'kind': 'abc', 'name': 'def'}]) diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py index 37691396db5e..3292bccf50e6 100644 --- a/gcloud/storage/bucket.py +++ b/gcloud/storage/bucket.py @@ -1,3 +1,5 @@ +import os + from gcloud.storage import exceptions from gcloud.storage.acl import BucketACL from gcloud.storage.acl import DefaultObjectACL @@ -230,9 +232,56 @@ def upload_file(self, filename, key=None): to the root of the bucket with the same name as on your local file system. """ + if key is None: + key = os.path.basename(filename) key = self.new_key(key) return key.set_contents_from_filename(filename) + def upload_file_object(self, fh, key=None): + # TODO: What do we do about overwriting data? + """Shortcut method to upload a file into this bucket. + + Use this method to quickly put a local file in Cloud Storage. + + For example:: + + >>> from gcloud import storage + >>> connection = storage.get_connection(project, email, key_path) + >>> bucket = connection.get_bucket('my-bucket') + >>> bucket.upload_file(open('~/my-file.txt'), 'remote-text-file.txt') + >>> print bucket.get_all_keys() + [] + + If you don't provide a key value, + we will try to upload the file using the local filename + as the key + (**not** the complete path):: + + >>> from gcloud import storage + >>> connection = storage.get_connection(project, email, key_path) + >>> bucket = connection.get_bucket('my-bucket') + >>> bucket.upload_file(open('~/my-file.txt')) + >>> print bucket.get_all_keys() + [] + + :type fh: file + :param fh: A file handle open for reading. + + :type key: string or :class:`gcloud.storage.key.Key` + :param key: The key (either an object or a remote path) + of where to put the file. + + If this is blank, + we will try to upload the file + to the root of the bucket + with the same name as on your local file system. + """ + if key: + key = self.new_key(key) + else: + key = self.new_key(os.path.basename(fh.name)) + return key.set_contents_from_file(fh) + def has_metadata(self, field=None): """Check if metadata is available locally. @@ -443,7 +492,9 @@ def save_acl(self, acl=None): if acl is None: return self - return self.patch_metadata({'acl': list(acl)}) + self.patch_metadata({'acl': list(acl)}) + self.reload_acl() + return self def clear_acl(self): """Remove all ACL rules from the bucket. @@ -517,12 +568,15 @@ def save_default_object_acl(self, acl=None): and save that. """ - acl = acl or self.default_object_acl + if acl is None: + acl = self.default_object_acl if acl is None: return self - return self.patch_metadata({'defaultObjectAcl': list(acl)}) + self.patch_metadata({'defaultObjectAcl': list(acl)}) + self.reload_default_object_acl() + return self def clear_default_object_acl(self): """Remove the Default Object ACL from this bucket.""" diff --git a/gcloud/storage/connection.py b/gcloud/storage/connection.py index 4c336634c6dd..e0a31bfdc14d 100644 --- a/gcloud/storage/connection.py +++ b/gcloud/storage/connection.py @@ -162,7 +162,7 @@ def make_request(self, method, url, data=None, content_type=None, return self.http.request(uri=url, method=method, headers=headers, body=data) - def api_request(self, method, path=None, query_params=None, + def api_request(self, method, path, query_params=None, data=None, content_type=None, api_base_url=None, api_version=None, expect_json=True): @@ -174,32 +174,40 @@ def api_request(self, method, path=None, query_params=None, :type method: string :param method: The HTTP method name (ie, ``GET``, ``POST``, etc). + Required. :type path: string :param path: The path to the resource (ie, ``'/b/bucket-name'``). + Required. :type query_params: dict :param query_params: A dictionary of keys and values to insert into - the query string of the URL. + the query string of the URL. Default is empty dict. :type data: string - :param data: The data to send as the body of the request. + :param data: The data to send as the body of the request. Default is the + empty string. :type content_type: string - :param content_type: The proper MIME type of the data provided. + :param content_type: The proper MIME type of the data provided. Default + is None. :type api_base_url: string :param api_base_url: The base URL for the API endpoint. Typically you won't have to provide this. + Default is the standard API base URL. :type api_version: string :param api_version: The version of the API to call. Typically you shouldn't provide this and instead use the default for the library. + Default is the latest API version supported by + gcloud-python. :type expect_json: bool :param expect_json: If True, this method will try to parse the response as JSON and raise an exception if that cannot be done. + Default is True. :raises: Exception if the response code is not 200 OK. """ @@ -230,7 +238,7 @@ def api_request(self, method, path=None, query_params=None, return content - def get_all_buckets(self, *args, **kwargs): + def get_all_buckets(self): """Get all buckets in the project. This will not populate the list of keys available @@ -253,7 +261,7 @@ def get_all_buckets(self, *args, **kwargs): return list(self) - def get_bucket(self, bucket_name, *args, **kwargs): + def get_bucket(self, bucket_name): """Get a bucket by name. If the bucket isn't found, @@ -312,7 +320,7 @@ def lookup(self, bucket_name): except exceptions.NotFoundError: return None - def create_bucket(self, bucket, *args, **kwargs): + def create_bucket(self, bucket): """Create a new bucket. For example:: @@ -335,7 +343,7 @@ def create_bucket(self, bucket, *args, **kwargs): data={'name': bucket.name}) return Bucket.from_dict(response, connection=self) - def delete_bucket(self, bucket, force=False, *args, **kwargs): + def delete_bucket(self, bucket, force=False): """Delete a bucket. You can use this method to delete a bucket by name, diff --git a/gcloud/storage/iterator.py b/gcloud/storage/iterator.py index fab97e26e2d0..b2dfcff8ba11 100644 --- a/gcloud/storage/iterator.py +++ b/gcloud/storage/iterator.py @@ -211,7 +211,7 @@ def has_more_data(self): def get_headers(self): start = self._bytes_written - end = self._bytes_written + self.key.CHUNK_SIZE + end = self._bytes_written + self.key.CHUNK_SIZE - 1 if self._total_bytes and end > self._total_bytes: end = '' diff --git a/gcloud/storage/key.py b/gcloud/storage/key.py index 2022ac5eccf1..722e3e393968 100644 --- a/gcloud/storage/key.py +++ b/gcloud/storage/key.py @@ -404,7 +404,9 @@ def save_acl(self, acl=None): if acl is None: return self - return self.patch_metadata({'acl': list(acl)}) + self.patch_metadata({'acl': list(acl)}) + self.reload_acl() + return self def clear_acl(self): """Remove all ACL rules from the key. diff --git a/gcloud/storage/test_bucket.py b/gcloud/storage/test_bucket.py index 15cc91d13ccc..647f3a16cb40 100644 --- a/gcloud/storage/test_bucket.py +++ b/gcloud/storage/test_bucket.py @@ -1,3 +1,5 @@ +import io + import unittest2 @@ -246,8 +248,22 @@ def test_delete_keys_miss(self): self.assertEqual(kw[1]['method'], 'DELETE') self.assertEqual(kw[1]['path'], '/b/%s/o/%s' % (NAME, NONESUCH)) - # See: https://github.com/GoogleCloudPlatform/gcloud-python/issues/137 - #def test_upload_file_default_key(self): + def test_upload_file_default_key(self): + from gcloud.test_credentials import _Monkey + from gcloud.storage import bucket as MUT + BASENAME = 'file.ext' + FILENAME = '/path/to/%s' % BASENAME + _uploaded = [] + class _Key(object): + def __init__(self, bucket, name): + self._bucket = bucket + self._name = name + def set_contents_from_filename(self, filename): + _uploaded.append((self._bucket, self._name, filename)) + bucket = self._makeOne() + with _Monkey(MUT, Key=_Key): + bucket.upload_file(FILENAME) + self.assertEqual(_uploaded, [(bucket, BASENAME, FILENAME)]) def test_upload_file_explicit_key(self): from gcloud.test_credentials import _Monkey @@ -266,6 +282,41 @@ def set_contents_from_filename(self, filename): bucket.upload_file(FILENAME, KEY) self.assertEqual(_uploaded, [(bucket, KEY, FILENAME)]) + def test_upload_file_object_no_key(self): + from gcloud.test_credentials import _Monkey + from gcloud.storage import bucket as MUT + FILENAME = 'file.txt' + FILEOBJECT = MockFile(FILENAME) + _uploaded = [] + class _Key(object): + def __init__(self, bucket, name): + self._bucket = bucket + self._name = name + def set_contents_from_file(self, fh): + _uploaded.append((self._bucket, self._name, fh)) + bucket = self._makeOne() + with _Monkey(MUT, Key=_Key): + bucket.upload_file_object(FILEOBJECT) + self.assertEqual(_uploaded, [(bucket, FILENAME, FILEOBJECT)]) + + def test_upload_file_object_explicit_key(self): + from gcloud.test_credentials import _Monkey + from gcloud.storage import bucket as MUT + FILENAME = 'file.txt' + FILEOBJECT = MockFile(FILENAME) + KEY = 'key' + _uploaded = [] + class _Key(object): + def __init__(self, bucket, name): + self._bucket = bucket + self._name = name + def set_contents_from_file(self, fh): + _uploaded.append((self._bucket, self._name, fh)) + bucket = self._makeOne() + with _Monkey(MUT, Key=_Key): + bucket.upload_file_object(FILEOBJECT, KEY) + self.assertEqual(_uploaded, [(bucket, KEY, FILEOBJECT)]) + def test_has_metdata_none_set(self): NONESUCH = 'nonesuch' bucket = self._makeOne() @@ -532,8 +583,7 @@ def test_save_acl_existing_set_new_passed(self): bucket = self._makeOne(connection, NAME, metadata) bucket.reload_acl() self.assertTrue(bucket.save_acl(new_acl) is bucket) - # See: https://github.com/GoogleCloudPlatform/gcloud-python/issues/138 - #self.assertEqual(list(bucket.acl), new_acl) + self.assertEqual(list(bucket.acl), new_acl) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') @@ -550,8 +600,7 @@ def test_clear_acl(self): bucket = self._makeOne(connection, NAME, metadata) bucket.reload_acl() self.assertTrue(bucket.clear_acl() is bucket) - # See: https://github.com/GoogleCloudPlatform/gcloud-python/issues/138 - #self.assertEqual(list(bucket.acl), []) + self.assertEqual(list(bucket.acl), []) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') @@ -628,52 +677,69 @@ def test_save_default_object_acl_none_set_none_passed(self): def test_save_default_object_acl_existing_set_none_passed(self): NAME = 'name' connection = _Connection({'foo': 'Foo', 'acl': []}) + connection = _Connection({'foo': 'Foo', 'acl': []}, + {'foo': 'Foo', 'acl': [], + 'defaultObjectAcl': []}, + ) metadata = {'defaultObjectAcl': []} bucket = self._makeOne(connection, NAME, metadata) bucket.reload_default_object_acl() self.assertTrue(bucket.save_default_object_acl() is bucket) kw = connection._requested - self.assertEqual(len(kw), 1) + self.assertEqual(len(kw), 2) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) self.assertEqual(kw[0]['data'], metadata) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(kw[1]['method'], 'GET') + self.assertEqual(kw[1]['path'], '/b/%s' % NAME) + self.assertEqual(kw[1]['query_params'], {'projection': 'full'}) def test_save_default_object_acl_existing_set_new_passed(self): NAME = 'name' ROLE = 'role' new_acl = [{'entity': 'allUsers', 'role': ROLE}] - connection = _Connection({'foo': 'Foo', 'acl': new_acl}) + connection = _Connection({'foo': 'Foo', 'acl': new_acl}, + {'foo': 'Foo', 'acl': new_acl, + 'defaultObjectAcl': new_acl}, + ) metadata = {'defaultObjectAcl': []} bucket = self._makeOne(connection, NAME, metadata) bucket.reload_default_object_acl() self.assertTrue(bucket.save_default_object_acl(new_acl) is bucket) - # See: https://github.com/GoogleCloudPlatform/gcloud-python/issues/139 - #self.assertEqual(list(bucket.default_object_acl), new_acl) + self.assertEqual(list(bucket.default_object_acl), new_acl) kw = connection._requested - self.assertEqual(len(kw), 1) + self.assertEqual(len(kw), 2) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) self.assertEqual(kw[0]['data'], {'defaultObjectAcl': new_acl}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(kw[1]['method'], 'GET') + self.assertEqual(kw[1]['path'], '/b/%s' % NAME) + self.assertEqual(kw[1]['query_params'], {'projection': 'full'}) def test_clear_default_object_acl(self): NAME = 'name' ROLE = 'role' old_acl = [{'entity': 'allUsers', 'role': ROLE}] - connection = _Connection({'foo': 'Foo', 'acl': []}) + connection = _Connection({'foo': 'Foo', 'acl': []}, + {'foo': 'Foo', 'acl': [], + 'defaultObjectAcl': []}, + ) metadata = {'defaultObjectAcl': old_acl} bucket = self._makeOne(connection, NAME, metadata) bucket.reload_default_object_acl() self.assertTrue(bucket.clear_default_object_acl() is bucket) - # See: https://github.com/GoogleCloudPlatform/gcloud-python/issues/139 - #self.assertEqual(list(bucket.default_object_acl), []) + self.assertEqual(list(bucket.default_object_acl), []) kw = connection._requested - self.assertEqual(len(kw), 1) + self.assertEqual(len(kw), 2) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - #self.assertEqual(kw[0]['data'], {'defaultObjectAcl': []}) + self.assertEqual(kw[0]['data'], {'defaultObjectAcl': []}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(kw[1]['method'], 'GET') + self.assertEqual(kw[1]['path'], '/b/%s' % NAME) + self.assertEqual(kw[1]['query_params'], {'projection': 'full'}) def test_make_public_defaults(self): from gcloud.storage.acl import ACL @@ -788,4 +854,10 @@ def delete_bucket(self, bucket, force=False): if not self._delete_ok: raise NotFoundError('miss', None) return True - + + +class MockFile(io.StringIO): + name = None + def __init__(self, name, buffer_ = None): + super(MockFile, self).__init__(buffer_) + self.name = name diff --git a/gcloud/storage/test_connection.py b/gcloud/storage/test_connection.py index 7c845cd72b50..ea793dafdf90 100644 --- a/gcloud/storage/test_connection.py +++ b/gcloud/storage/test_connection.py @@ -203,38 +203,16 @@ def test_make_request_w_extra_headers(self): def test_api_request_defaults(self): PROJECT = 'project' + PATH = '/path/required' conn = self._makeOne(PROJECT) URI = '/'.join([conn.API_BASE_URL, 'storage', conn.API_VERSION, - # see https://github.com/GoogleCloudPlatform/ - # gcloud-python/issues/140 - #'?project=%s' % PROJECT, - ]) + 'None?project=%s' % PROJECT # XXX + ]) + '%s?project=%s' % (PATH, PROJECT) http = conn._http = Http({'status': '200', 'content-type': 'application/json', }, '{}') - self.assertEqual(conn.api_request('GET'), {}) - self.assertEqual(http._called_with['method'], 'GET') - self.assertEqual(http._called_with['uri'], URI) - self.assertEqual(http._called_with['body'], None) - self.assertEqual(http._called_with['headers'], - {'Accept-Encoding': 'gzip', - 'Content-Length': 0, - }) - - def test_api_request_w_path(self): - PROJECT = 'project' - conn = self._makeOne(PROJECT) - URI = '/'.join([conn.API_BASE_URL, - 'storage', - conn.API_VERSION, - '?project=%s' % PROJECT, - ]) - http = conn._http = Http({'status': '200', - 'content-type': 'application/json', - }, '{}') - self.assertEqual(conn.api_request('GET', '/'), {}) + self.assertEqual(conn.api_request('GET', PATH), {}) self.assertEqual(http._called_with['method'], 'GET') self.assertEqual(http._called_with['uri'], URI) self.assertEqual(http._called_with['body'], None) diff --git a/gcloud/storage/test_iterator.py b/gcloud/storage/test_iterator.py index c8c8feb87573..8841ddae3e46 100644 --- a/gcloud/storage/test_iterator.py +++ b/gcloud/storage/test_iterator.py @@ -206,29 +206,29 @@ def test_ctor(self): def test__iter__(self): response1 = _Response(status=200) - response1['content-range'] = '0-10/15' + response1['content-range'] = '0-9/15' response2 = _Response(status=200) - response2['content-range'] = '11-14/15' - connection = _Connection((response1, '01234567890'), - (response2, '1234'), + response2['content-range'] = '10-14/15' + connection = _Connection((response1, '0123456789'), + (response2, '01234'), ) key = _Key(connection) iterator = self._makeOne(key) chunks = list(iterator) self.assertEqual(len(chunks), 2) - self.assertEqual(chunks[0], '01234567890') - self.assertEqual(chunks[1], '1234') + self.assertEqual(chunks[0], '0123456789') + self.assertEqual(chunks[1], '01234') self.assertEqual(iterator._bytes_written, 15) self.assertEqual(iterator._total_bytes, 15) kws = connection._requested self.assertEqual(kws[0]['method'], 'GET') - self.assertEqual(kws[0]['url'], + self.assertEqual(kws[0]['url'], 'http://example.com/b/name/o/key?alt=media') - self.assertEqual(kws[0]['headers'], {'Range': 'bytes=0-10'}) + self.assertEqual(kws[0]['headers'], {'Range': 'bytes=0-9'}) self.assertEqual(kws[1]['method'], 'GET') - self.assertEqual(kws[1]['url'], + self.assertEqual(kws[1]['url'], 'http://example.com/b/name/o/key?alt=media') - self.assertEqual(kws[1]['headers'], {'Range': 'bytes=11-'}) + self.assertEqual(kws[1]['headers'], {'Range': 'bytes=10-'}) def test_reset(self): connection = _Connection() @@ -275,7 +275,7 @@ def test_get_headers_new(self): iterator = self._makeOne(key) headers = iterator.get_headers() self.assertEqual(len(headers), 1) - self.assertEqual(headers['Range'], 'bytes=0-10') + self.assertEqual(headers['Range'], 'bytes=0-9') def test_get_headers_ok(self): connection = _Connection() @@ -285,7 +285,7 @@ def test_get_headers_ok(self): iterator._total_bytes = 1000 headers = iterator.get_headers() self.assertEqual(len(headers), 1) - self.assertEqual(headers['Range'], 'bytes=10-20') + self.assertEqual(headers['Range'], 'bytes=10-19') def test_get_headers_off_end(self): connection = _Connection() @@ -313,7 +313,7 @@ def test_get_next_chunk_underflow(self): def test_get_next_chunk_200(self): response = _Response(status=200) - response['content-range'] = '0-10/100' + response['content-range'] = '0-9/100' connection = _Connection((response, 'CHUNK')) key = _Key(connection) iterator = self._makeOne(key) @@ -323,9 +323,9 @@ def test_get_next_chunk_200(self): self.assertEqual(iterator._total_bytes, 100) kw, = connection._requested self.assertEqual(kw['method'], 'GET') - self.assertEqual(kw['url'], + self.assertEqual(kw['url'], 'http://example.com/b/name/o/key?alt=media') - self.assertEqual(kw['headers'], {'Range': 'bytes=0-10'}) + self.assertEqual(kw['headers'], {'Range': 'bytes=0-9'}) def test_get_next_chunk_206(self): response = _Response(status=206) @@ -338,9 +338,9 @@ def test_get_next_chunk_206(self): self.assertEqual(iterator._bytes_written, len(chunk)) kw, = connection._requested self.assertEqual(kw['method'], 'GET') - self.assertEqual(kw['url'], + self.assertEqual(kw['url'], 'http://example.com/b/name/o/key?alt=media') - self.assertEqual(kw['headers'], {'Range': 'bytes=0-10'}) + self.assertEqual(kw['headers'], {'Range': 'bytes=0-9'}) def test_get_next_chunk_416(self): response = _Response(status=416) diff --git a/gcloud/storage/test_key.py b/gcloud/storage/test_key.py index e261178bcbbd..28db03c400f3 100644 --- a/gcloud/storage/test_key.py +++ b/gcloud/storage/test_key.py @@ -509,8 +509,7 @@ def test_save_acl_existing_set_new_passed(self): key = self._makeOne(bucket, KEY, metadata) key.reload_acl() self.assertTrue(key.save_acl(new_acl) is key) - # See: https://github.com/GoogleCloudPlatform/gcloud-python/issues/138 - #self.assertEqual(list(bucket.acl), new_acl) + self.assertEqual(list(key.acl), new_acl) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') @@ -528,8 +527,7 @@ def test_clear_acl(self): key = self._makeOne(bucket, KEY, metadata) key.reload_acl() self.assertTrue(key.clear_acl() is key) - # See: https://github.com/GoogleCloudPlatform/gcloud-python/issues/138 - #self.assertEqual(list(key.acl), []) + self.assertEqual(list(key.acl), []) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH')