Add support for streaming object responses

When getting objects, sometimes you want a stream rather than reading
the whole value or passing to a file-like object. Add a stream_object
method that returns an iterator over the content to be returned.

Change-Id: I6f7ae89799e733657adc9989b3accfdf6e3f2f82
This commit is contained in:
Monty Taylor 2018-07-26 10:28:02 -04:00
parent de981a324a
commit 96eac6a018
No known key found for this signature in database
GPG Key ID: 7BAE94BC7141A594
3 changed files with 70 additions and 7 deletions

View File

@ -7793,12 +7793,7 @@ class OpenStackCloud(_normalize.Normalizer):
:raises: OpenStackCloudException on operation error.
"""
endpoint = self._get_object_endpoint(container, obj, query_string)
try:
return self._object_store_client.get(endpoint, stream=stream)
except exc.OpenStackCloudHTTPError as e:
if e.response.status_code == 404:
return None
raise
return self._object_store_client.get(endpoint, stream=stream)
def _get_object_endpoint(self, container, obj, query_string):
endpoint = '{container}/{object}'.format(
@ -7808,8 +7803,33 @@ class OpenStackCloud(_normalize.Normalizer):
endpoint=endpoint, query_string=query_string)
return endpoint
def stream_object(
self, container, obj, query_string=None, resp_chunk_size=1024):
"""Download the content via a streaming iterator.
:param string container: name of the container.
:param string obj: name of the object.
:param string query_string:
query args for uri. (delimiter, prefix, etc.)
:param int resp_chunk_size:
chunk size of data to read. Only used if the results are
:returns:
An iterator over the content or None if the object is not found.
:raises: OpenStackCloudException on operation error.
"""
try:
with self.get_object_raw(
container, obj, query_string=query_string) as response:
for ret in response.iter_content(chunk_size=resp_chunk_size):
yield ret
except exc.OpenStackCloudHTTPError as e:
if e.response.status_code == 404:
return
raise
def get_object(self, container, obj, query_string=None,
resp_chunk_size=1024, outfile=None):
resp_chunk_size=1024, outfile=None, stream=False):
"""Get the headers and body of an object
:param string container: name of the container.

View File

@ -347,6 +347,45 @@ class TestObject(BaseTestObject):
self.assertEqual((response_headers, text), resp)
def test_stream_object(self):
text = b'test body'
self.register_uris([
dict(method='GET', uri=self.object_endpoint,
headers={
'Content-Length': '20304400896',
'Content-Type': 'application/octet-stream',
'Accept-Ranges': 'bytes',
'Last-Modified': 'Thu, 15 Dec 2016 13:34:14 GMT',
'Etag': '"b5c454b44fbd5344793e3fb7e3850768"',
'X-Timestamp': '1481808853.65009',
'X-Trans-Id': 'tx68c2a2278f0c469bb6de1-005857ed80dfw1',
'Date': 'Mon, 19 Dec 2016 14:24:00 GMT',
'X-Static-Large-Object': 'True',
'X-Object-Meta-Mtime': '1481513709.168512',
},
text='test body')])
response_text = b''
for data in self.cloud.stream_object(self.container, self.object):
response_text += data
self.assert_calls()
self.assertEqual(text, response_text)
def test_stream_object_not_found(self):
self.register_uris([
dict(method='GET', uri=self.object_endpoint, status_code=404),
])
response_text = b''
for data in self.cloud.stream_object(self.container, self.object):
response_text += data
self.assert_calls()
self.assertEqual(b'', response_text)
def test_get_object_not_found(self):
self.register_uris([dict(method='GET',
uri=self.object_endpoint, status_code=404)])

View File

@ -0,0 +1,4 @@
---
features:
- |
Added ``stream_object`` method for getting object content in an iterator.