Skip to content

Commit

Permalink
Azure Backend: Implement open() which supports partial reads (#65)
Browse files Browse the repository at this point in the history
* Implement open() in azure-store which supports partial reads from the blob store.

* Add test for seek and tell, as well as fixing the implementation of IOInterface in the azure backend.

* Fix weird typing error on python 3.

* Add more tests for seeking.
  • Loading branch information
Cornelius Riemenschneider committed Nov 15, 2017
1 parent db065d7 commit 9d08f81
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 9 deletions.
75 changes: 66 additions & 9 deletions simplekv/net/azurestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,12 @@ def iter_keys(self, prefix=u""):

def _open(self, key):
with map_azure_exceptions(key=key):
output_stream = io.BytesIO()
self.block_blob_service.get_blob_to_stream(self.container, key,
output_stream)
output_stream.seek(0)
return output_stream
return IOInterface(self.block_blob_service, self.container, key)

def _put(self, key, data):
with map_azure_exceptions(key=key):
if isinstance(data, binary_type):
self.block_blob_service.create_blob_from_bytes(self.container,
self.block_blob_service.create_blob_from_bytes(self.container,
key, data)
else:
raise TypeError('Wrong type, expecting str or bytes.')
return key

def _put_file(self, key, file):
Expand Down Expand Up @@ -128,3 +121,67 @@ def pickle_azure_store(store):
store.create_if_missing)

copyreg.pickle(AzureBlockBlobStore, pickle_azure_store)


class IOInterface(io.BufferedIOBase):
"""
Class which provides a file-like interface to selectively read from a blob in the blob store.
"""
def __init__(self, block_blob_service, container_name, key):
super(IOInterface, self).__init__()
self.block_blob_service = block_blob_service
self.container_name = container_name
self.key = key

blob = self.block_blob_service.get_blob_properties(container_name, key)
self.size = blob.properties.content_length
self.pos = 0

def tell(self):
"""Returns he current offset as int. Always >= 0."""
return self.pos

def read(self, size=-1):
"""Returns 'size' amount of bytes or less if there is no more data.
If no size is given all data is returned. size can be >= 0."""
with map_azure_exceptions(key=self.key):
if size < 0:
size = self.size - self.pos

end = min(self.pos + size - 1, self.size)
if self.pos > end:
return b''
b = self.block_blob_service.get_blob_to_bytes(
self.container_name,
self.key,
start_range=self.pos,
end_range=end) # end_range is inclusive
self.pos += len(b.content)
return b.content

def seek(self, offset, whence=0):
"""Move to a new offset either relative or absolute. whence=0 is
absolute, whence=1 is relative, whence=2 is relative to the end.
Any relative or absolute seek operation which would result in a
negative position is undefined and that case can be ignored
in the implementation.
Any seek operation which moves the position after the stream
should succeed. tell() should report that position and read()
should return an empty bytes object."""
if whence == 0:
if offset < 0:
raise IOError('seek would move position outside the file')
self.pos = offset
elif whence == 1:
if self.pos + offset < 0:
raise IOError('seek would move position outside the file')
self.pos += offset
elif whence == 2:
if self.size + offset < 0:
raise IOError('seek would move position outside the file')
self.pos = self.size + offset

def seekable(self):
return True
26 changes: 26 additions & 0 deletions tests/test_azure_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,32 @@ def store(self):
public=False)
s.delete_container(container)

def test_open_seek_and_tell(self, store, key, long_value):
store.put(key, long_value)
ok = store.open(key)
assert ok.seekable()
ok.seek(10)
assert ok.tell() == 10
ok.seek(-6, 1)
assert ok.tell() == 4
with pytest.raises(IOError):
ok.seek(-1, 0)
with pytest.raises(IOError):
ok.seek(-6, 1)
with pytest.raises(IOError):
ok.seek(-len(long_value) - 1, 2)

assert ok.tell() == 4
assert long_value[4:5] == ok.read(1)
assert ok.tell() == 5
ok.seek(-1, 2)
length_lv = len(long_value)
assert long_value[length_lv - 1:length_lv] == ok.read(1)
assert ok.tell() == length_lv
ok.seek(length_lv + 10, 0)
assert ok.tell() == length_lv + 10
assert len(ok.read()) == 0


class TestExtendedKeysAzureStorage(TestAzureStorage, ExtendedKeyspaceTests):
@pytest.fixture
Expand Down

0 comments on commit 9d08f81

Please sign in to comment.