Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: googleapis/python-storage
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.37.0
Choose a base ref
...
head repository: googleapis/python-storage
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.37.1
Choose a head ref
  • 3 commits
  • 6 files changed
  • 2 contributors

Commits on Mar 29, 2021

  1. fix: silence expected errors for routine operations on BlobReader (#400)

    Two fixes for BlobReader:
    - Checksums are not supported for BlobReader's chunked downloads, so set checksum=None to silence log warnings (and add a note to the docstring explaining this).
    - In Python, read() on files at EOF should return an empty result, but not raise an error. Stop BlobReader from emitting RequestRangeNotSatisfiable errors at EOF.
    
    Fixes: #399
    andrewsg authored Mar 29, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d52853b View commit details

Commits on Apr 2, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    416bcd4 View commit details

Commits on Apr 5, 2021

  1. chore: release 1.37.1 (#401)

    Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
    release-please[bot] authored Apr 5, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7fb2ee4 View commit details
Showing with 60 additions and 15 deletions.
  1. +8 −0 CHANGELOG.md
  2. +5 −0 google/cloud/storage/blob.py
  3. +16 −4 google/cloud/storage/fileio.py
  4. +1 −1 google/cloud/storage/version.py
  5. +3 −0 tests/system/test_system.py
  6. +27 −10 tests/unit/test_fileio.py
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,14 @@

[1]: https://pypi.org/project/google-cloud-storage/#history

### [1.37.1](https://www.github.com/googleapis/python-storage/compare/v1.37.0...v1.37.1) (2021-04-02)


### Bug Fixes

* Ensure consistency check in test runs even if expected error occurs ([#402](https://www.github.com/googleapis/python-storage/issues/402)) ([416bcd4](https://www.github.com/googleapis/python-storage/commit/416bcd42406ec57e51f04e5d9b0c58509f80520c))
* silence expected errors for routine operations on BlobReader ([#400](https://www.github.com/googleapis/python-storage/issues/400)) ([d52853b](https://www.github.com/googleapis/python-storage/commit/d52853b420f50012e02c395f5407e3018922c048))

## [1.37.0](https://www.github.com/googleapis/python-storage/compare/v1.36.2...v1.37.0) (2021-03-24)


5 changes: 5 additions & 0 deletions google/cloud/storage/blob.py
Original file line number Diff line number Diff line change
@@ -3434,6 +3434,11 @@ def open(
latest generation number and set it; or, if the generation is known, set
it manually, for instance with bucket.blob(generation=123456).
Checksumming (hashing) to verify data integrity is disabled for reads
using this feature because reads are implemented using request ranges,
which do not provide checksums to validate. See
https://cloud.google.com/storage/docs/hashes-etags for details.
:type mode: str
:param mode:
(Optional) A mode string, as per standard Python `open()` semantics.The first
20 changes: 16 additions & 4 deletions google/cloud/storage/fileio.py
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@

import io

from google.api_core.exceptions import RequestRangeNotSatisfiable

# Resumable uploads require a chunk size of precisely a multiple of 256 KiB.
CHUNK_SIZE_MULTIPLE = 256 * 1024 # 256 KiB
DEFAULT_CHUNK_SIZE = 40 * 1024 * 1024 # 40 MiB
@@ -92,10 +94,20 @@ def read(self, size=-1):
else:
fetch_end = None

# Download the blob.
result += self._blob.download_as_bytes(
start=fetch_start, end=fetch_end, **self._download_kwargs
)
# Download the blob. Checksumming must be disabled as we are using
# chunked downloads, and the server only knows the checksum of the
# entire file.
try:
result += self._blob.download_as_bytes(
start=fetch_start,
end=fetch_end,
checksum=None,
**self._download_kwargs
)
except RequestRangeNotSatisfiable:
# We've reached the end of the file. Python file objects should
# return an empty response in this case, not raise an error.
pass

# If more bytes were read than is immediately needed, buffer the
# remainder and then trim the result.
2 changes: 1 addition & 1 deletion google/cloud/storage/version.py
Original file line number Diff line number Diff line change
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = "1.37.0"
__version__ = "1.37.1"
3 changes: 3 additions & 0 deletions tests/system/test_system.py
Original file line number Diff line number Diff line change
@@ -1134,6 +1134,9 @@ def test_blobwriter_and_blobreader(self):
file_obj.read(256 * 1024 * 2), reader.read(256 * 1024 * 2)
)
self.assertEqual(file_obj.read(), reader.read())
# End of file reached; further reads should be blank but not
# raise an error.
self.assertEqual(b"", reader.read())

def test_blobwriter_and_blobreader_text_mode(self):
blob = self.bucket.blob("MultibyteTextFile")
37 changes: 27 additions & 10 deletions tests/unit/test_fileio.py
Original file line number Diff line number Diff line change
@@ -17,9 +17,11 @@
import unittest
import mock
import io
from google.cloud.storage.fileio import BlobReader, BlobWriter, SlidingBuffer
import string

from google.cloud.storage.fileio import BlobReader, BlobWriter, SlidingBuffer
from google.api_core.exceptions import RequestRangeNotSatisfiable

TEST_TEXT_DATA = string.ascii_lowercase + "\n" + string.ascii_uppercase + "\n"
TEST_BINARY_DATA = TEST_TEXT_DATA.encode("utf-8")
TEST_MULTIBYTE_TEXT_DATA = u"あいうえおかきくけこさしすせそたちつてと"
@@ -50,7 +52,7 @@ def read_from_fake_data(start=0, end=None, **_):
# Read and trigger the first download of chunk_size.
self.assertEqual(reader.read(1), TEST_BINARY_DATA[0:1])
blob.download_as_bytes.assert_called_once_with(
start=0, end=8, **download_kwargs
start=0, end=8, checksum=None, **download_kwargs
)

# Read from buffered data only.
@@ -61,21 +63,36 @@ def read_from_fake_data(start=0, end=None, **_):
self.assertEqual(reader.read(8), TEST_BINARY_DATA[4:12])
self.assertEqual(reader._pos, 12)
self.assertEqual(blob.download_as_bytes.call_count, 2)
blob.download_as_bytes.assert_called_with(start=8, end=16, **download_kwargs)
blob.download_as_bytes.assert_called_with(
start=8, end=16, checksum=None, **download_kwargs
)

# Read a larger amount, requiring a download larger than chunk_size.
self.assertEqual(reader.read(16), TEST_BINARY_DATA[12:28])
self.assertEqual(reader._pos, 28)
self.assertEqual(blob.download_as_bytes.call_count, 3)
blob.download_as_bytes.assert_called_with(start=16, end=28, **download_kwargs)
blob.download_as_bytes.assert_called_with(
start=16, end=28, checksum=None, **download_kwargs
)

# Read all remaining data.
self.assertEqual(reader.read(), TEST_BINARY_DATA[28:])
self.assertEqual(blob.download_as_bytes.call_count, 4)
blob.download_as_bytes.assert_called_with(start=28, end=None, **download_kwargs)
blob.download_as_bytes.assert_called_with(
start=28, end=None, checksum=None, **download_kwargs
)

reader.close()

def test_416_error_handled(self):
blob = mock.Mock()
blob.download_as_bytes = mock.Mock(
side_effect=RequestRangeNotSatisfiable("message")
)

reader = BlobReader(blob)
self.assertEqual(reader.read(), b"")

def test_readline(self):
blob = mock.Mock()

@@ -87,12 +104,12 @@ def read_from_fake_data(start=0, end=None, **_):

# Read a line. With chunk_size=10, expect three chunks downloaded.
self.assertEqual(reader.readline(), TEST_BINARY_DATA[:27])
blob.download_as_bytes.assert_called_with(start=20, end=30)
blob.download_as_bytes.assert_called_with(start=20, end=30, checksum=None)
self.assertEqual(blob.download_as_bytes.call_count, 3)

# Read another line.
self.assertEqual(reader.readline(), TEST_BINARY_DATA[27:])
blob.download_as_bytes.assert_called_with(start=50, end=60)
blob.download_as_bytes.assert_called_with(start=50, end=60, checksum=None)
self.assertEqual(blob.download_as_bytes.call_count, 6)

blob.size = len(TEST_BINARY_DATA)
@@ -101,7 +118,7 @@ def read_from_fake_data(start=0, end=None, **_):
# Read all lines. The readlines algorithm will attempt to read past the end of the last line once to verify there is no more to read.
self.assertEqual(b"".join(reader.readlines()), TEST_BINARY_DATA)
blob.download_as_bytes.assert_called_with(
start=len(TEST_BINARY_DATA), end=len(TEST_BINARY_DATA) + 10
start=len(TEST_BINARY_DATA), end=len(TEST_BINARY_DATA) + 10, checksum=None
)
self.assertEqual(blob.download_as_bytes.call_count, 13)

@@ -371,7 +388,7 @@ def test_seek(self):
with self.assertRaises(ValueError):
pos = buff.tell()
buff.seek(len(TEST_BINARY_DATA) + 1)
self.assertEqual(pos, buff.tell())
self.assertEqual(pos, buff.tell())

# Read 8 bytes, test seek backwards, read again, and flush.
self.assertEqual(buff.read(8), TEST_BINARY_DATA[:8])
@@ -384,7 +401,7 @@ def test_seek(self):
with self.assertRaises(ValueError):
pos = buff.tell()
buff.seek(0)
self.assertEqual(pos, buff.tell())
self.assertEqual(pos, buff.tell())

def test_close(self):
buff = SlidingBuffer()