Skip to content

Commit b8016d9

Browse files
authored
[Blob][BugFix]Incorrect handling of Short Reads in create_blob_from_s… (#583)
* [Blob][BugFix]Incorrect handling of Short Reads in create_blob_from_stream Currently if the user want to upload a blob with short reads, only the data read by the first short reads call is uploaded. This commit is to fix this bug #512
1 parent aa237f3 commit b8016d9

File tree

4 files changed

+636
-1
lines changed

4 files changed

+636
-1
lines changed

azure-storage-blob/azure/storage/blob/blockblobservice.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,20 @@ def create_blob_from_stream(
574574
progress_callback(0, count)
575575

576576
data = stream.read(count)
577+
data_chunk = data # to store the chunk of data read from stream each time
578+
579+
# keep reading from stream util length of data >= count or reaching the end of stream
580+
while len(data) < count and len(data_chunk) is not 0:
581+
data_chunk = stream.read(count)
582+
data += data_chunk
583+
584+
if len(data) < count:
585+
raise ValueError('Parameter:count is greater than the amount of data in the stream,'
586+
'please specify a valid count')
587+
588+
if len(data) > count:
589+
data = data[0:count]
590+
577591
resp = self._put_blob(
578592
container_name=container_name,
579593
blob_name=blob_name,

tests/blob/test_block_blob.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# license information.
77
# --------------------------------------------------------------------------
88
import os
9+
import io
910
import unittest
1011

1112
from azure.common import AzureHttpError
@@ -15,7 +16,6 @@
1516
BlockBlobService,
1617
ContentSettings,
1718
)
18-
from azure.storage.blob.models import StandardBlobTier
1919
from tests.testcase import (
2020
StorageTestCase,
2121
TestMode,
@@ -520,6 +520,48 @@ def test_create_blob_from_path_with_properties(self):
520520
self.assertEqual(properties.content_settings.content_type, content_settings.content_type)
521521
self.assertEqual(properties.content_settings.content_language, content_settings.content_language)
522522

523+
@record
524+
def test_createBlobFromStream_when_short_read_the_whole_stream(self):
525+
# Arrange
526+
blob_name = self._get_blob_reference()
527+
content = b"Example blob content."
528+
stream = ShortReader(io.BytesIO(content), 10)
529+
upload_size = len(content)
530+
531+
# Act
532+
self.bs.create_blob_from_stream(self.container_name, blob_name, stream, upload_size)
533+
534+
# Assert
535+
uploaded_blob = self.bs.get_blob_to_bytes(self.container_name, blob_name)
536+
self.assertEquals(content, uploaded_blob.content)
537+
538+
@record
539+
def test_createBlobFromStream_when_short_read_designated_size_of_stream(self):
540+
# Arrange
541+
blob_name = self._get_blob_reference()
542+
content = b"Example blob content."
543+
stream = ShortReader(io.BytesIO(content), 10)
544+
upload_size = 15 # designated size blob we want to upload
545+
546+
# Act
547+
self.bs.create_blob_from_stream(self.container_name, blob_name, stream, upload_size)
548+
549+
# Assert
550+
uploaded_blob = self.bs.get_blob_to_bytes(self.container_name, blob_name)
551+
self.assertEqual(upload_size, len(uploaded_blob.content))
552+
553+
def test_createBlobFromStream_when_designated_size_larger_than_the_actual_stream_size(self):
554+
555+
# Arrange
556+
blob_name = self._get_blob_reference()
557+
content = b"Example blob content."
558+
stream = ShortReader(io.BytesIO(content), 10)
559+
upload_size = 100 # designated size blob we want to upload
560+
561+
# Assert
562+
with self.assertRaises(ValueError):
563+
self.bs.create_blob_from_stream(self.container_name, blob_name, stream, upload_size)
564+
523565
def test_create_blob_from_stream_chunked_upload(self):
524566
# parallel tests introduce random order of requests, can only run live
525567
if TestMode.need_recording_file(self.test_mode):
@@ -771,6 +813,20 @@ def test_create_blob_with_md5_chunked(self):
771813

772814
# Assert
773815

816+
817+
class ShortReader(io.RawIOBase):
818+
"""IOBase wrapper to limit read sizes, producing short reads"""
819+
820+
def __init__(self, reader, max_read_size):
821+
self._reader = reader
822+
self.max_read_size = max_read_size
823+
824+
def readinto(self, buf):
825+
buf_size = len(buf)
826+
read_size = min(buf_size, self.max_read_size)
827+
data = self._reader.read(read_size)
828+
buf[0:len(data)] = data
829+
return len(data)
774830
#------------------------------------------------------------------------------
775831
if __name__ == '__main__':
776832
unittest.main()
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
interactions:
2+
- request:
3+
body: Example blob co
4+
headers:
5+
Connection:
6+
- keep-alive
7+
Content-Length:
8+
- '15'
9+
User-Agent:
10+
- Azure-Storage/1.4.0-1.5.0 (Python CPython 3.7.3; Windows 10)
11+
x-ms-blob-type:
12+
- BlockBlob
13+
x-ms-client-request-id:
14+
- 31e38124-8174-11e9-b09e-001a7dda7113
15+
x-ms-date:
16+
- Tue, 28 May 2019 18:12:46 GMT
17+
x-ms-version:
18+
- '2018-11-09'
19+
method: PUT
20+
uri: https://storagename.blob.core.windows.net/utcontainer7fa321b4/blob7fa321b4
21+
response:
22+
body:
23+
string: ''
24+
headers:
25+
Content-Length:
26+
- '0'
27+
Content-MD5:
28+
- 2bNSt3gr222I6yQwqlz12w==
29+
Date:
30+
- Tue, 28 May 2019 18:12:45 GMT
31+
ETag:
32+
- '"0x8D6E39816280908"'
33+
Last-Modified:
34+
- Tue, 28 May 2019 18:12:46 GMT
35+
Server:
36+
- Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
37+
x-ms-request-id:
38+
- fa3febc7-601e-00ea-6c80-1574da000000
39+
x-ms-request-server-encrypted:
40+
- 'true'
41+
x-ms-version:
42+
- '2018-11-09'
43+
status:
44+
code: 201
45+
message: Created
46+
- request:
47+
body: null
48+
headers:
49+
Connection:
50+
- keep-alive
51+
User-Agent:
52+
- Azure-Storage/1.4.0-1.5.0 (Python CPython 3.7.3; Windows 10)
53+
x-ms-client-request-id:
54+
- 31f05c0a-8174-11e9-a545-001a7dda7113
55+
x-ms-date:
56+
- Tue, 28 May 2019 18:12:46 GMT
57+
x-ms-range:
58+
- bytes=0-33554431
59+
x-ms-version:
60+
- '2018-11-09'
61+
method: GET
62+
uri: https://storagename.blob.core.windows.net/utcontainer7fa321b4/blob7fa321b4
63+
response:
64+
body:
65+
string: Example blob co
66+
headers:
67+
Accept-Ranges:
68+
- bytes
69+
Content-Length:
70+
- '15'
71+
Content-Range:
72+
- bytes 0-14/15
73+
Content-Type:
74+
- application/octet-stream
75+
Date:
76+
- Tue, 28 May 2019 18:12:45 GMT
77+
ETag:
78+
- '"0x8D6E39816280908"'
79+
Last-Modified:
80+
- Tue, 28 May 2019 18:12:46 GMT
81+
Server:
82+
- Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
83+
Vary:
84+
- Origin
85+
x-ms-blob-content-md5:
86+
- 2bNSt3gr222I6yQwqlz12w==
87+
x-ms-blob-type:
88+
- BlockBlob
89+
x-ms-creation-time:
90+
- Tue, 28 May 2019 18:12:46 GMT
91+
x-ms-lease-state:
92+
- available
93+
x-ms-lease-status:
94+
- unlocked
95+
x-ms-request-id:
96+
- fa3febd8-601e-00ea-7a80-1574da000000
97+
x-ms-server-encrypted:
98+
- 'true'
99+
x-ms-tag-count:
100+
- '0'
101+
x-ms-version:
102+
- '2018-11-09'
103+
status:
104+
code: 206
105+
message: Partial Content
106+
version: 1

0 commit comments

Comments
 (0)