Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ca88ac2
gen code
l0lawrence Feb 10, 2026
b9bb2c2
patch
l0lawrence Feb 10, 2026
0375995
imports
l0lawrence Feb 10, 2026
2343cab
async patch
l0lawrence Feb 10, 2026
04dd250
edit
l0lawrence Feb 10, 2026
05c4ae7
make models not typedDict
l0lawrence Feb 10, 2026
b7244d0
patch update
l0lawrence Feb 10, 2026
45f2ae1
update
l0lawrence Feb 10, 2026
b766a06
missed import
l0lawrence Feb 10, 2026
d69a467
import
l0lawrence Feb 10, 2026
bb29e86
conftest
l0lawrence Feb 10, 2026
ae1d4b6
models
l0lawrence Feb 11, 2026
58d4b5e
deserialize
l0lawrence Feb 11, 2026
bb4da34
order of params in stage_block()
l0lawrence Feb 11, 2026
b54cfb8
blob -> block_blob for query()
l0lawrence Feb 11, 2026
067d1df
patch updates
l0lawrence Feb 11, 2026
df3bf8a
deserilizaed
l0lawrence Feb 11, 2026
d79a511
blob clinet
l0lawrence Feb 11, 2026
418b716
client updates
l0lawrence Feb 11, 2026
8d50117
datetime
l0lawrence Feb 11, 2026
b916276
patch etag quote???
l0lawrence Feb 12, 2026
46a484b
snapshot
l0lawrence Feb 12, 2026
87fd92c
explicitly check modified access conditions is not None
l0lawrence Feb 12, 2026
1b6c906
custom range policy
l0lawrence Feb 13, 2026
293f043
model base changes**
l0lawrence Feb 13, 2026
74f076c
regen off of feature branch w/ one client + rangeHeader()
l0lawrence Feb 13, 2026
d47a25b
content-type fixes cat made
l0lawrence Feb 13, 2026
f1d5cd8
x-ms-requires-sync
l0lawrence Feb 13, 2026
bbae834
clientName the old operations
l0lawrence Feb 13, 2026
1c59a77
model base deserialization edit
l0lawrence Feb 13, 2026
2904d1f
signedidentitifers
l0lawrence Feb 13, 2026
9c07bf4
models
l0lawrence Feb 13, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
UserDelegationKey,
Services
)
from ._generated.models import RehydratePriority
from ._generated.azure.storage.blobs.models import RehydratePriority
from ._models import (
BlobType,
BlockState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@
)
from ._download import StorageStreamDownloader
from ._encryption import StorageEncryptionMixin, _ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION
from ._generated import AzureBlobStorage
from ._generated.models import CpkInfo
from ._generated.azure.storage.blobs import AzureBlobStorage
from ._generated.azure.storage.blobs.models import CpkInfo
from ._lease import BlobLeaseClient
from ._models import BlobBlock, BlobProperties, BlobQueryError, BlobType, PageRange, PageRangePaged
from ._quick_query_helper import BlobQueryReader
Expand Down Expand Up @@ -186,7 +186,18 @@ 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, get_api_version(kwargs), base_url=self.url, pipeline=self._pipeline)
# Build a URL without the snapshot query parameter for the generated client.
# The snapshot is passed as a method parameter by operations that need it, so including
# it in the base URL would cause it to appear twice in requests.
client_query_str, _ = self._format_query_string(sas_token, self._raw_credential)
client_url = _format_url(
container_name=self.container_name,
scheme=self.scheme,
blob_name=self.blob_name,
query_str=client_query_str,
hostname=self._hosts[self._location_mode],
)
self._client = AzureBlobStorage(client_url, base_url=client_url, version=get_api_version(kwargs), pipeline=self._pipeline)
self._configure_encryption(kwargs)

def __enter__(self) -> Self:
Expand Down Expand Up @@ -870,7 +881,7 @@ def query_blob(self, query_expression: str, **kwargs: Any) -> BlobQueryReader:
raise ValueError("Customer provided encryption key must be used over HTTPS.")
options, delimiter = _quick_query_options(self.snapshot, query_expression, **kwargs)
try:
headers, raw_response_body = self._client.blob.query(**options)
headers, raw_response_body = self._client.block_blob.query(**options)
except HttpResponseError as error:
process_storage_error(error)
return BlobQueryReader(
Expand Down Expand Up @@ -1344,7 +1355,7 @@ def set_legal_hold(self, legal_hold: bool, **kwargs: Any) -> Dict[str, Union[str

version_id = get_version_id(self.version_id, kwargs)
return cast(Dict[str, Union[str, datetime, bool]], self._client.blob.set_legal_hold(
legal_hold, version_id=version_id, cls=return_response_headers, **kwargs))
legal_hold=legal_hold, version_id=version_id, cls=return_response_headers, **kwargs))

@distributed_trace
def create_page_blob(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from ._deserialize import deserialize_blob_stream
from ._encryption import modify_user_agent_for_encryption, _ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION
from ._generated.models import (
from ._generated.azure.storage.blobs.models import (
AppendPositionAccessConditions,
BlobHTTPHeaders,
BlockList,
Expand Down Expand Up @@ -61,7 +61,7 @@

if TYPE_CHECKING:
from urllib.parse import ParseResult
from ._generated import AzureBlobStorage
from ._generated.azure.storage.blobs import AzureBlobStorage
from ._models import ContentSettings
from ._shared.models import StorageConfiguration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
from ._container_client import ContainerClient
from ._deserialize import service_properties_deserialize, service_stats_deserialize
from ._encryption import StorageEncryptionMixin
from ._generated import AzureBlobStorage
from ._generated.models import KeyInfo, StorageServiceProperties
from ._generated.azure.storage.blobs import AzureBlobStorage
from ._generated.azure.storage.blobs.models import KeyInfo, StorageServiceProperties
from ._list_blobs_helper import FilteredBlobPaged
from ._models import BlobProperties, ContainerProperties, ContainerPropertiesPaged, CorsRule
from ._serialize import get_api_version
Expand Down Expand Up @@ -127,7 +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, get_api_version(kwargs), base_url=self.url, pipeline=self._pipeline)
self._client = AzureBlobStorage(self.url, base_url=self.url, version=get_api_version(kwargs), 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 @@ -29,8 +29,8 @@
from ._deserialize import deserialize_container_properties
from ._download import StorageStreamDownloader
from ._encryption import StorageEncryptionMixin
from ._generated import AzureBlobStorage
from ._generated.models import SignedIdentifier
from ._generated.azure.storage.blobs import AzureBlobStorage
from ._generated.azure.storage.blobs.models import SignedIdentifier, SignedIdentifiers
from ._lease import BlobLeaseClient
from ._list_blobs_helper import (
BlobNamesPaged,
Expand Down Expand Up @@ -167,7 +167,7 @@ def close(self) -> None:
self._client.close()

def _build_generated_client(self) -> AzureBlobStorage:
return AzureBlobStorage(self.url, self._api_version, base_url=self.url, pipeline=self._pipeline)
return AzureBlobStorage(self.url, base_url=self.url, version=self._api_version, pipeline=self._pipeline)

def _format_url(self, hostname):
return _format_url(
Expand Down Expand Up @@ -782,7 +782,7 @@ def set_container_access_policy(
timeout = kwargs.pop('timeout', None)
try:
return cast(Dict[str, Union[str, datetime]], self._client.container.set_access_policy(
container_acl=signed_identifiers or None,
container_acl=SignedIdentifiers(items_property=signed_identifiers) if signed_identifiers else None,
timeout=timeout,
access=public_access,
lease_access_conditions=access_conditions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
from azure.core import MatchConditions
from azure.core.pipeline.transport import HttpRequest
from ._blob_client_helpers import _generic_delete_blob_options
from ._generated import AzureBlobStorage
from ._generated.azure.storage.blobs import AzureBlobStorage
from ._models import BlobProperties
from ._shared.base_client import parse_query

if TYPE_CHECKING:
from azure.storage.blob import RehydratePriority
from urllib.parse import ParseResult
from ._generated.models import LeaseAccessConditions, ModifiedAccessConditions
from ._generated.azure.storage.blobs.models import LeaseAccessConditions, ModifiedAccessConditions
from ._models import PremiumPageBlobTier, StandardBlobTier


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

if TYPE_CHECKING:
from azure.core.pipeline import PipelineResponse
from ._generated.models import (
from ._generated.azure.storage.blobs.models import (
BlobItemInternal,
BlobTags,
PageList,
Expand Down Expand Up @@ -87,14 +87,35 @@ def deserialize_ors_policies(policy_dictionary: Optional[Dict[str, str]]) -> Opt
return result_list


class _DownloadResponse:
"""Wrapper for download response that holds both the stream and properties."""
def __init__(
self,
stream: Any,
properties: BlobProperties,
response: "PipelineResponse"
) -> None:
self._stream = stream
self.properties = properties
self.response = response.http_response
# Content-Length header contains the size of the response body
self.content_length = int(response.http_response.headers.get('Content-Length', 0))

def __iter__(self):
return iter(self._stream)

def __aiter__(self):
return self._stream.__aiter__()


def deserialize_blob_stream(
response: "PipelineResponse",
obj: Any,
headers: Dict[str, Any]
) -> Tuple["LocationMode", Any]:
) -> Tuple["LocationMode", "_DownloadResponse"]:
blob_properties = deserialize_blob_properties(response, obj, headers)
obj.properties = blob_properties
return response.http_response.location_mode, obj
download_response = _DownloadResponse(obj, blob_properties, response)
return response.http_response.location_mode, download_response


def deserialize_container_properties(
Expand Down Expand Up @@ -156,11 +177,11 @@ def get_blob_properties_from_generated_code(generated: "BlobItemInternal") -> Bl
blob.name = generated.name.content #type: ignore
blob_type = get_enum_value(generated.properties.blob_type)
blob.blob_type = BlobType(blob_type)
blob.etag = generated.properties.etag
blob.etag = generated.properties.e_tag
blob.deleted = generated.deleted
blob.snapshot = generated.snapshot
blob.is_append_blob_sealed = generated.properties.is_sealed
blob.metadata = generated.metadata.additional_properties if generated.metadata else {} # type: ignore [assignment]
blob.metadata = dict(generated.metadata) if generated.metadata else {} # type: ignore [assignment]
blob.encrypted_metadata = generated.metadata.encrypted if generated.metadata else None
blob.lease = LeaseProperties._from_generated(generated) # pylint: disable=protected-access
blob.copy = CopyProperties._from_generated(generated) # pylint: disable=protected-access
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
if TYPE_CHECKING:
from codecs import IncrementalDecoder
from ._encryption import _EncryptionData
from ._generated import AzureBlobStorage
from ._generated.operations import BlobOperations
from ._generated.azure.storage.blobs import AzureBlobStorage
from ._generated.azure.storage.blobs._operations import _BlobClientOperationsMixin as BlobOperations
from ._models import BlobProperties
from ._shared.models import StorageConfiguration

Expand Down Expand Up @@ -244,7 +244,7 @@ def _download_chunk(self, chunk_start: int, chunk_end: int) -> Tuple[bytes, int]

# This makes sure that if_match is set so that we can validate
# that subsequent downloads are to an unmodified blob
if self.request_options.get("modified_access_conditions"):
if self.request_options.get("modified_access_conditions") is not None:
self.request_options["modified_access_conditions"].if_match = response.properties.etag

return chunk_data, content_length
Expand Down Expand Up @@ -542,7 +542,7 @@ def _initial_request(self):
except HttpResponseError:
pass

if not self._download_complete and self._request_options.get("modified_access_conditions"):
if not self._download_complete and self._request_options.get("modified_access_conditions") is not None:
self._request_options["modified_access_conditions"].if_match = response.properties.etag

return response
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Release History

## 1.0.0b1 (1970-01-01)

### Other Changes

- Initial version
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Copyright (c) Microsoft Corporation.

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include *.md
include LICENSE
include azure/storage/blobs/py.typed
recursive-include tests *.py
recursive-include samples *.py *.md
include azure/__init__.py
include azure/storage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Azure Storage Blob client library for Python
<!-- write necessary description of service -->

## Getting started

### Install the package

```bash
python -m pip install azure-storage-blob
```

#### Prequisites

- Python 3.9 or later is required to use this package.
- You need an [Azure subscription][azure_sub] to use this package.
- An existing Azure Storage Blob instance.

#### Create with an Azure Active Directory Credential
To use an [Azure Active Directory (AAD) token credential][authenticate_with_token],
provide an instance of the desired credential type obtained from the
[azure-identity][azure_identity_credentials] library.

To authenticate with AAD, you must first [pip][pip] install [`azure-identity`][azure_identity_pip]

After setup, you can choose which type of [credential][azure_identity_credentials] from azure.identity to use.
As an example, [DefaultAzureCredential][default_azure_credential] can be used to authenticate the client:

Set the values of the client ID, tenant ID, and client secret of the AAD application as environment variables:
`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`

Use the returned token credential to authenticate the client:

```python
>>> from azure.storage.blobs import ServiceClient
>>> from azure.identity import DefaultAzureCredential
>>> client = ServiceClient(endpoint='<endpoint>', credential=DefaultAzureCredential())
```

## Examples

```python
>>> from azure.storage.blobs import ServiceClient
>>> from azure.identity import DefaultAzureCredential
>>> from azure.core.exceptions import HttpResponseError

>>> client = ServiceClient(endpoint='<endpoint>', credential=DefaultAzureCredential())
>>> try:
<!-- write test code here -->
except HttpResponseError as e:
print('service responds error: {}'.format(e.response.json()))

```

## Contributing

This project welcomes contributions and suggestions. Most contributions require
you to agree to a Contributor License Agreement (CLA) declaring that you have
the right to, and actually do, grant us the rights to use your contribution.
For details, visit https://cla.microsoft.com.

When you submit a pull request, a CLA-bot will automatically determine whether
you need to provide a CLA and decorate the PR appropriately (e.g., label,
comment). Simply follow the instructions provided by the bot. You will only
need to do this once across all repos using our CLA.

This project has adopted the
[Microsoft Open Source Code of Conduct][code_of_conduct]. For more information,
see the Code of Conduct FAQ or contact opencode@microsoft.com with any
additional questions or comments.

<!-- LINKS -->
[code_of_conduct]: https://opensource.microsoft.com/codeofconduct/
[authenticate_with_token]: https://docs.microsoft.com/azure/cognitive-services/authentication?tabs=powershell#authenticate-with-an-authentication-token
[azure_identity_credentials]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity#credentials
[azure_identity_pip]: https://pypi.org/project/azure-identity/
[default_azure_credential]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity#defaultazurecredential
[pip]: https://pypi.org/project/pip/
[azure_sub]: https://azure.microsoft.com/free/
Original file line number Diff line number Diff line change
@@ -1,29 +1,5 @@
# coding=utf-8
# --------------------------------------------------------------------------
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# Code generated by Microsoft (R) AutoRest Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
# pylint: disable=wrong-import-position

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from ._patch import * # pylint: disable=unused-wildcard-import

from ._azure_blob_storage import AzureBlobStorage # type: ignore

try:
from ._patch import __all__ as _patch_all
from ._patch import *
except ImportError:
_patch_all = []
from ._patch import patch_sdk as _patch_sdk

__all__ = [
"AzureBlobStorage",
]
__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore

_patch_sdk()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"apiVersion": "2026-04-06"
}
Loading
Loading