Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sdk/storage/azure-storage-blob/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
## 12.31.0b1 (Unreleased)

### Features Added
- The service-calculated CRC64 is now surfaced as `content_crc64`
on the response of `BlobClient` upload operations
(`stage_block`, `stage_block_from_url`, `upload_page`, `upload_pages_from_url`,
`append_block`, `append_block_from_url`, `upload_blob`, and `upload_blob_from_url`)
in addition to `content_md5` when a content MD5 is provided with the request.

## 12.30.0 (2026-06-08)

Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-blob/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/storage/azure-storage-blob",
"Tag": "python/storage/azure-storage-blob_b09e37b521"
"Tag": "python/storage/azure-storage-blob_3903d1bfe0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ async def upload( # pylint: disable=too-many-locals
response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
response_headers["x-ms-content-crc64"] = self._deserialize(
"bytearray", response.headers.get("x-ms-content-crc64")
)
response_headers["x-ms-client-request-id"] = self._deserialize(
"str", response.headers.get("x-ms-client-request-id")
)
Expand Down Expand Up @@ -522,6 +525,9 @@ async def put_blob_from_url( # pylint: disable=too-many-locals
response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
response_headers["x-ms-content-crc64"] = self._deserialize(
"bytearray", response.headers.get("x-ms-content-crc64")
)
response_headers["x-ms-client-request-id"] = self._deserialize(
"str", response.headers.get("x-ms-client-request-id")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,9 @@ def upload( # pylint: disable=inconsistent-return-statements,too-many-locals
response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
response_headers["x-ms-content-crc64"] = self._deserialize(
"bytearray", response.headers.get("x-ms-content-crc64")
)
response_headers["x-ms-client-request-id"] = self._deserialize(
"str", response.headers.get("x-ms-client-request-id")
)
Expand Down Expand Up @@ -1181,6 +1184,9 @@ def put_blob_from_url( # pylint: disable=inconsistent-return-statements,too-man
response_headers["ETag"] = self._deserialize("str", response.headers.get("ETag"))
response_headers["Last-Modified"] = self._deserialize("rfc-1123", response.headers.get("Last-Modified"))
response_headers["Content-MD5"] = self._deserialize("bytearray", response.headers.get("Content-MD5"))
response_headers["x-ms-content-crc64"] = self._deserialize(
"bytearray", response.headers.get("x-ms-content-crc64")
)
response_headers["x-ms-client-request-id"] = self._deserialize(
"str", response.headers.get("x-ms-client-request-id")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"2026-02-06",
"2026-04-06",
"2026-06-06",
"2026-10-06",
]


Expand Down
4 changes: 4 additions & 0 deletions sdk/storage/azure-storage-blob/tests/test_append_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ def test_append_block_with_md5(self, **kwargs):
assert resp["blob_committed_block_count"] == 1
assert resp["etag"] is not None
assert resp["last_modified"] is not None
assert resp["content_md5"] is not None
assert resp["content_crc64"] is not None

# Assert

Expand Down Expand Up @@ -410,6 +412,8 @@ def test_append_block_from_url_and_validate_content_md5(self, **kwargs):
assert resp.get("blob_committed_block_count") == 1
assert resp.get("etag") is not None
assert resp.get("last_modified") is not None
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

# Assert the destination blob is constructed correctly
destination_blob_properties = destination_blob_client.get_blob_properties()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ async def test_append_block_with_md5(self, **kwargs):
assert resp["blob_committed_block_count"] == 1
assert resp["etag"] is not None
assert resp["last_modified"] is not None
assert resp["content_md5"] is not None
assert resp["content_crc64"] is not None

# Assert

Expand Down Expand Up @@ -409,6 +411,8 @@ async def test_append_block_from_url_and_validate_content_md5(self, **kwargs):
assert resp.get("blob_committed_block_count") == 1
assert resp.get("etag") is not None
assert resp.get("last_modified") is not None
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

# Assert the destination blob is constructed correctly
destination_blob_properties = await destination_blob_client.get_blob_properties()
Expand Down
8 changes: 6 additions & 2 deletions sdk/storage/azure-storage-blob/tests/test_block_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,9 +585,11 @@ def test_upload_blob_from_url_with_source_content_md5(self, **kwargs):
new_blob = self.bsc.get_blob_client(self.container_name, blob_name)

# Assert
new_blob.upload_blob_from_url(
resp = new_blob.upload_blob_from_url(
source_blob_url, include_source_blob_properties=True, source_content_md5=source_md5
)
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None
with pytest.raises(HttpResponseError):
new_blob.upload_blob_from_url(
source_blob_url, include_source_blob_properties=False, source_content_md5=bad_source_md5
Expand Down Expand Up @@ -718,9 +720,11 @@ def test_put_block_with_md5(self, **kwargs):
blob = self._create_blob()

# Act
blob.stage_block(1, b"block", validate_content=True)
resp = blob.stage_block(1, b"block", validate_content=True)

# Assert
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

@BlobPreparer()
@recorded_by_proxy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -618,9 +618,11 @@ async def test_upload_blob_from_url_with_source_content_md5(self, **kwargs):
new_blob = self.bsc.get_blob_client(self.container_name, blob_name)

# Assert
await new_blob.upload_blob_from_url(
resp = await new_blob.upload_blob_from_url(
source_blob_url, include_source_blob_properties=True, source_content_md5=source_md5
)
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None
with pytest.raises(HttpResponseError):
await new_blob.upload_blob_from_url(
source_blob_url, include_source_blob_properties=False, source_content_md5=bad_source_md5
Expand Down Expand Up @@ -803,9 +805,11 @@ async def test_put_block_with_md5(self, **kwargs):
blob = await self._create_blob()

# Act
await blob.stage_block(1, b"block", validate_content=True)
resp = await blob.stage_block(1, b"block", validate_content=True)

# Assert
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

@BlobPreparer()
@recorded_by_proxy_async
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,15 @@ def test_put_block_from_url_and_validate_content_md5(self, **kwargs):
src_md5 = calculate_content_md5(self.source_blob_data)

# Act part 1: put block from url with md5 validation
dest_blob.stage_block_from_url(
resp = dest_blob.stage_block_from_url(
block_id=1,
source_url=self.source_blob_url,
source_content_md5=src_md5,
source_offset=0,
source_length=8 * 1024,
)
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

# Assert block was staged
committed, uncommitted = dest_blob.get_block_list("all")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,15 @@ async def test_put_block_from_url_and_vldte_content_md5(self, **kwargs):
src_md5 = calculate_content_md5(self.source_blob_data)

# Act part 1: put block from url with md5 validation
await dest_blob.stage_block_from_url(
resp = await dest_blob.stage_block_from_url(
block_id=1,
source_url=self.source_blob_url,
source_content_md5=src_md5,
source_offset=0,
source_length=8 * 1024,
)
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

# Assert block was staged
committed, uncommitted = await dest_blob.get_block_list("all")
Expand Down
6 changes: 5 additions & 1 deletion sdk/storage/azure-storage-blob/tests/test_page_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,9 +417,11 @@ def test_update_page_with_md5(self, **kwargs):

# Act
data = self.get_random_bytes(512)
blob.upload_page(data, offset=0, length=512, validate_content=True)
resp = blob.upload_page(data, offset=0, length=512, validate_content=True)

# Assert
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

@BlobPreparer()
@recorded_by_proxy
Expand Down Expand Up @@ -752,6 +754,8 @@ def test_upload_pages_from_url_and_validate_content_md5(self, **kwargs):
)
assert resp.get("etag") is not None
assert resp.get("last_modified") is not None
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

# Assert the destination blob is constructed correctly
blob_properties = destination_blob_client.get_blob_properties()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,10 @@ async def test_update_page_with_md5(self, **kwargs):

# Act
data = self.get_random_bytes(512)
await blob.upload_page(data, offset=0, length=512, validate_content=True)
resp = await blob.upload_page(data, offset=0, length=512, validate_content=True)
# Assert
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

@BlobPreparer()
@recorded_by_proxy_async
Expand Down Expand Up @@ -697,6 +699,8 @@ async def test_upload_pages_from_url_and_validate_content_md5(self, **kwargs):
)
assert resp.get("etag") is not None
assert resp.get("last_modified") is not None
assert resp.get("content_md5") is not None
assert resp.get("content_crc64") is not None

# Assert the destination blob is constructed correctly
blob_properties = await destination_blob_client.get_blob_properties()
Expand Down