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
15 changes: 15 additions & 0 deletions sdk/storage/azure-storage-blob/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@
This replaces `INCREMENTAL_COPY_OF_EARLIER_VERSION_SNAPSHOT_NOT_ALLOWED` which has been deprecated.
- Added support for the keywords `access_tier_if_modified_since` and `access_tier_if_unmodified_since` to
conditionally perform `BlobClient.delete_blob` operation.
- Added support for the keyword `source_cpk` for `BlobClient`'s `upload_blob_from_url`,
`stage_block_from_url`, `upload_pages_from_url`, and `append_block_from_url` APIs
to re-encrypt data automatically by the service through a `CustomerProvidedEncryptionKey`.
- Added support for the keyword `user_delegation_tid` to `BlobServiceClient.get_user_delegation_key` API, which
can be used in `generate_blob_sas` and `generate_container_sas` to specify the Tenant ID that is authorized
to use the generated SAS URL. Note that `user_delegation_tid` must be used together with `user_delegation_oid`.
- Added support for the keyword `request_headers` to `generate_blob_sas` and `generate_container_sas`,
which specifies a set of headers and their corresponding values that must be
present in the request header when using the generated SAS.
- Added support for the keyword `request_query_params` to `generate_blob_sas` and `generate_container_sas`,
which specifies a set of query parameters and their corresponding values that must be
present in the request URL when using the generated SAS.

### Other Changes
- Bumped minimum `azure-core` dependency to 1.37.0.

## 12.28.0 (2026-01-06)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"azure.storage.blob.models.RetentionPolicy": null,
"azure.storage.blob.models.SequenceNumberAccessConditions": null,
"azure.storage.blob.models.SignedIdentifier": null,
"azure.storage.blob.models.SourceCpkInfo": null,
"azure.storage.blob.models.SourceModifiedAccessConditions": null,
"azure.storage.blob.models.StaticWebsite": null,
"azure.storage.blob.models.StorageError": null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,7 @@ def __init__(
self._raw_credential = credential if credential else sas_token
self._query_str, credential = self._format_query_string(sas_token, credential, snapshot=self.snapshot)
super(BlobClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
self._client = AzureBlobStorage(self.url, base_url=self.url, pipeline=self._pipeline)
self._client._config.version = get_api_version(kwargs) # type: ignore [assignment]
self._client = AzureBlobStorage(self.url, get_api_version(kwargs), base_url=self.url, pipeline=self._pipeline)
self._configure_encryption(kwargs)

def __enter__(self) -> Self:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
DeleteSnapshotsOptionType,
ModifiedAccessConditions,
QueryRequest,
SequenceNumberAccessConditions
SequenceNumberAccessConditions,
SourceCpkInfo
)
from ._models import (
BlobBlock,
Expand Down Expand Up @@ -215,6 +216,13 @@ def _upload_blob_from_url_options(source_url: str, **kwargs: Any) -> Dict[str, A
cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
encryption_algorithm=cpk.algorithm)
source_cpk = kwargs.pop('source_cpk', None)
source_cpk_info = None
if source_cpk:
source_cpk_info = SourceCpkInfo(
source_encryption_key=source_cpk.key_value,
source_encryption_key_sha256=source_cpk.key_hash,
source_encryption_algorithm=source_cpk.algorithm
)

options = {
'copy_source_authorization': source_authorization,
Expand All @@ -231,17 +239,12 @@ def _upload_blob_from_url_options(source_url: str, **kwargs: Any) -> Dict[str, A
'source_modified_access_conditions': get_source_conditions(kwargs),
'cpk_info': cpk_info,
'cpk_scope_info': get_cpk_scope_info(kwargs),
'source_cpk_info': source_cpk_info,
'headers': headers,
}
options.update(kwargs)
if not overwrite and not _any_conditions(**options):
options['modified_access_conditions'].if_none_match = '*'
if source_cpk:
options.update({
'source_encryption_key': source_cpk.key_value,
'source_encryption_key_sha256': source_cpk.key_hash,
'source_encryption_algorithm': source_cpk.algorithm
})
return options

def _download_blob_options(
Expand Down Expand Up @@ -766,6 +769,14 @@ def _stage_block_from_url_options(
cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
encryption_algorithm=cpk.algorithm)
source_cpk = kwargs.pop('source_cpk', None)
source_cpk_info = None
if source_cpk:
source_cpk_info = SourceCpkInfo(
source_encryption_key=source_cpk.key_value,
source_encryption_key_sha256=source_cpk.key_hash,
source_encryption_algorithm=source_cpk.algorithm
)

options = {
'copy_source_authorization': source_authorization,
'file_request_intent': source_token_intent,
Expand All @@ -778,15 +789,10 @@ def _stage_block_from_url_options(
'lease_access_conditions': access_conditions,
'cpk_scope_info': cpk_scope_info,
'cpk_info': cpk_info,
'source_cpk_info': source_cpk_info,
'cls': return_response_headers,
}
options.update(kwargs)
if source_cpk:
options.update({
'source_encryption_key': source_cpk.key_value,
'source_encryption_key_sha256': source_cpk.key_hash,
'source_encryption_algorithm': source_cpk.algorithm
})
return options

def _get_block_list_result(blocks: BlockList) -> Tuple[List[BlobBlock], List[BlobBlock]]:
Expand Down Expand Up @@ -1056,6 +1062,13 @@ def _upload_pages_from_url_options(
cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
encryption_algorithm=cpk.algorithm)
source_cpk = kwargs.pop('source_cpk', None)
source_cpk_info = None
if source_cpk:
source_cpk_info = SourceCpkInfo(
source_encryption_key=source_cpk.key_value,
source_encryption_key_sha256=source_cpk.key_hash,
source_encryption_algorithm=source_cpk.algorithm
)

options = {
'copy_source_authorization': source_authorization,
Expand All @@ -1072,15 +1085,10 @@ def _upload_pages_from_url_options(
'source_modified_access_conditions': source_mod_conditions,
'cpk_scope_info': cpk_scope_info,
'cpk_info': cpk_info,
'source_cpk_info': source_cpk_info,
'cls': return_response_headers
}
options.update(kwargs)
if source_cpk:
options.update({
'source_encryption_key': source_cpk.key_value,
'source_encryption_key_sha256': source_cpk.key_hash,
'source_encryption_algorithm': source_cpk.algorithm
})
return options

def _clear_page_options(
Expand Down Expand Up @@ -1210,6 +1218,13 @@ def _append_block_from_url_options(
encryption_algorithm=cpk.algorithm
)
source_cpk = kwargs.pop('source_cpk', None)
source_cpk_info = None
if source_cpk:
source_cpk_info = SourceCpkInfo(
source_encryption_key=source_cpk.key_value,
source_encryption_key_sha256=source_cpk.key_hash,
source_encryption_algorithm=source_cpk.algorithm
)

options = {
'copy_source_authorization': source_authorization,
Expand All @@ -1225,18 +1240,11 @@ def _append_block_from_url_options(
'source_modified_access_conditions': source_mod_conditions,
'cpk_scope_info': cpk_scope_info,
'cpk_info': cpk_info,
'source_cpk_info': source_cpk_info,
'cls': return_response_headers,
'timeout': kwargs.pop('timeout', None)
}
options.update(kwargs)

if source_cpk:
options.update({
'source_encryption_key': source_cpk.key_value,
'source_encryption_key_sha256': source_cpk.key_hash,
'source_encryption_algorithm': source_cpk.algorithm
})

return options

def _seal_append_blob_options(**kwargs: Any) -> Dict[str, Any]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ def __init__(
_, sas_token = parse_query(parsed_url.query)
self._query_str, credential = self._format_query_string(sas_token, credential)
super(BlobServiceClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
self._client = AzureBlobStorage(self.url, base_url=self.url, pipeline=self._pipeline)
self._client._config.version = get_api_version(kwargs) # type: ignore [assignment]
self._client = AzureBlobStorage(self.url, get_api_version(kwargs), base_url=self.url, pipeline=self._pipeline)
self._configure_encryption(kwargs)

def __enter__(self) -> Self:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,7 @@ def close(self) -> None:
self._client.close()

def _build_generated_client(self) -> AzureBlobStorage:
client = AzureBlobStorage(self.url, base_url=self.url, pipeline=self._pipeline)
client._config.version = self._api_version # type: ignore [assignment] # pylint: disable=protected-access
return client
return AzureBlobStorage(self.url, self._api_version, base_url=self.url, pipeline=self._pipeline)

def _format_url(self, hostname):
return _format_url(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,16 @@ class AzureBlobStorage: # pylint: disable=client-accepts-api-version-keyword
:param url: The URL of the service account, container, or blob that is the target of the
desired operation. Required.
:type url: str
:param version: Specifies the version of the operation to use for this request. Required.
:type version: str
:param base_url: Service URL. Required. Default value is "".
:type base_url: str
:keyword version: Specifies the version of the operation to use for this request. Default value
is "2026-04-06". Note that overriding this default value may result in unsupported behavior.
:paramtype version: str
"""

def __init__( # pylint: disable=missing-client-constructor-parameter-credential
self, url: str, base_url: str = "", **kwargs: Any
self, url: str, version: str, base_url: str = "", **kwargs: Any
) -> None:
self._config = AzureBlobStorageConfiguration(url=url, **kwargs)
self._config = AzureBlobStorageConfiguration(url=url, version=version, **kwargs)

_policies = kwargs.pop("policies", None)
if _policies is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

from typing import Any, Literal
from typing import Any

from azure.core.pipeline import policies

Expand All @@ -22,16 +22,15 @@ class AzureBlobStorageConfiguration: # pylint: disable=too-many-instance-attrib
:param url: The URL of the service account, container, or blob that is the target of the
desired operation. Required.
:type url: str
:keyword version: Specifies the version of the operation to use for this request. Default value
is "2026-04-06". Note that overriding this default value may result in unsupported behavior.
:paramtype version: str
:param version: Specifies the version of the operation to use for this request. Required.
:type version: str
"""

def __init__(self, url: str, **kwargs: Any) -> None:
version: Literal["2026-04-06"] = kwargs.pop("version", "2026-04-06")

def __init__(self, url: str, version: str, **kwargs: Any) -> None:
if url is None:
raise ValueError("Parameter 'url' must not be None.")
if version is None:
raise ValueError("Parameter 'version' must not be None.")

self.url = url
self.version = version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -821,13 +821,20 @@ def serialize_basic(cls, data, data_type, **kwargs):
:param str data_type: Type of object in the iterable.
:rtype: str, int, float, bool
:return: serialized object
:raises TypeError: raise if data_type is not one of str, int, float, bool.
"""
custom_serializer = cls._get_custom_serializers(data_type, **kwargs)
if custom_serializer:
return custom_serializer(data)
if data_type == "str":
return cls.serialize_unicode(data)
return eval(data_type)(data) # nosec # pylint: disable=eval-used
if data_type == "int":
return int(data)
if data_type == "float":
return float(data)
if data_type == "bool":
return bool(data)
raise TypeError("Unknown basic data type: {}".format(data_type))

@classmethod
def serialize_unicode(cls, data):
Expand Down Expand Up @@ -1757,7 +1764,7 @@ def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return
:param str data_type: deserialization data type.
:return: Deserialized basic type.
:rtype: str, int, float or bool
:raises TypeError: if string format is not valid.
:raises TypeError: if string format is not valid or data_type is not one of str, int, float, bool.
"""
# If we're here, data is supposed to be a basic type.
# If it's still an XML node, take the text
Expand All @@ -1783,7 +1790,11 @@ def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return

if data_type == "str":
return self.deserialize_unicode(attr)
return eval(data_type)(attr) # nosec # pylint: disable=eval-used
if data_type == "int":
return int(attr)
if data_type == "float":
return float(attr)
raise TypeError("Unknown basic data type: {}".format(data_type))

@staticmethod
def deserialize_unicode(data):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,16 @@ class AzureBlobStorage: # pylint: disable=client-accepts-api-version-keyword
:param url: The URL of the service account, container, or blob that is the target of the
desired operation. Required.
:type url: str
:param version: Specifies the version of the operation to use for this request. Required.
:type version: str
:param base_url: Service URL. Required. Default value is "".
:type base_url: str
:keyword version: Specifies the version of the operation to use for this request. Default value
is "2026-04-06". Note that overriding this default value may result in unsupported behavior.
:paramtype version: str
"""

def __init__( # pylint: disable=missing-client-constructor-parameter-credential
self, url: str, base_url: str = "", **kwargs: Any
self, url: str, version: str, base_url: str = "", **kwargs: Any
) -> None:
self._config = AzureBlobStorageConfiguration(url=url, **kwargs)
self._config = AzureBlobStorageConfiguration(url=url, version=version, **kwargs)

_policies = kwargs.pop("policies", None)
if _policies is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

from typing import Any, Literal
from typing import Any

from azure.core.pipeline import policies

Expand All @@ -22,16 +22,15 @@ class AzureBlobStorageConfiguration: # pylint: disable=too-many-instance-attrib
:param url: The URL of the service account, container, or blob that is the target of the
desired operation. Required.
:type url: str
:keyword version: Specifies the version of the operation to use for this request. Default value
is "2026-04-06". Note that overriding this default value may result in unsupported behavior.
:paramtype version: str
:param version: Specifies the version of the operation to use for this request. Required.
:type version: str
"""

def __init__(self, url: str, **kwargs: Any) -> None:
version: Literal["2026-04-06"] = kwargs.pop("version", "2026-04-06")

def __init__(self, url: str, version: str, **kwargs: Any) -> None:
if url is None:
raise ValueError("Parameter 'url' must not be None.")
if version is None:
raise ValueError("Parameter 'version' must not be None.")

self.url = url
self.version = version
Expand Down
Loading