From 0391516b45d376f5212001e14db406c3138b2bb4 Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Tue, 10 Mar 2026 16:23:46 -0700 Subject: [PATCH 1/2] Fix typing contract for max_concurrency in Fileshare client --- .../azure-storage-file-share/assets.json | 2 +- .../azure/storage/fileshare/_download.py | 17 +++---- .../azure/storage/fileshare/_file_client.py | 5 +- .../azure/storage/fileshare/_file_client.pyi | 4 +- .../storage/fileshare/_shared/constants.py | 2 + .../storage/fileshare/aio/_download_async.py | 17 +++---- .../fileshare/aio/_file_client_async.py | 5 +- .../fileshare/aio/_file_client_async.pyi | 4 +- .../tests/test_file.py | 45 ++++++++++++++++++ .../tests/test_file_async.py | 47 +++++++++++++++++++ 10 files changed, 125 insertions(+), 23 deletions(-) diff --git a/sdk/storage/azure-storage-file-share/assets.json b/sdk/storage/azure-storage-file-share/assets.json index 5d09afd5ce09..8abfb36115df 100644 --- a/sdk/storage/azure-storage-file-share/assets.json +++ b/sdk/storage/azure-storage-file-share/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/storage/azure-storage-file-share", - "Tag": "python/storage/azure-storage-file-share_c5fe7fb7b7" + "Tag": "python/storage/azure-storage-file-share_1fb2aaa99c" } diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_download.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_download.py index 734f1a46a6a9..935b17ebfde9 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_download.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_download.py @@ -17,6 +17,7 @@ from azure.core.tracing.common import with_current_context from ._shared.request_handlers import validate_and_format_range_headers from ._shared.response_handlers import parse_length_from_content_range, process_storage_error +from ._shared.constants import DEFAULT_MAX_CONCURRENCY if TYPE_CHECKING: from ._generated.operations import FileOperations @@ -217,7 +218,7 @@ def __init__( start_range: Optional[int] = None, end_range: Optional[int] = None, validate_content: bool = None, # type: ignore [assignment] - max_concurrency: int = 1, + max_concurrency: Optional[int] = None, name: str = None, # type: ignore [assignment] path: str = None, # type: ignore [assignment] share: str = None, # type: ignore [assignment] @@ -233,7 +234,7 @@ def __init__( self._config = config self._start_range = start_range self._end_range = end_range - self._max_concurrency = max_concurrency + self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY self._encoding = encoding self._validate_content = validate_content self._progress_hook = kwargs.pop('progress_hook', None) @@ -398,7 +399,7 @@ def readall(self) -> bytes: return data.decode(self._encoding) # type: ignore [return-value] return data - def content_as_bytes(self, max_concurrency=1): + def content_as_bytes(self, max_concurrency=None): """DEPRECATED: Download the contents of this file. This operation is blocking until all data is downloaded. @@ -414,10 +415,10 @@ def content_as_bytes(self, max_concurrency=1): "content_as_bytes is deprecated, use readall instead", DeprecationWarning ) - self._max_concurrency = max_concurrency + self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY return self.readall() - def content_as_text(self, max_concurrency=1, encoding="UTF-8"): + def content_as_text(self, max_concurrency=None, encoding="UTF-8"): """DEPRECATED: Download the contents of this file, and decode as text. This operation is blocking until all data is downloaded. @@ -435,7 +436,7 @@ def content_as_text(self, max_concurrency=1, encoding="UTF-8"): "content_as_text is deprecated, use readall instead", DeprecationWarning ) - self._max_concurrency = max_concurrency + self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY self._encoding = encoding return self.readall() @@ -501,7 +502,7 @@ def readinto(self, stream: IO[bytes]) -> int: downloader.process_chunk(chunk) return self.size - def download_to_stream(self, stream, max_concurrency=1): + def download_to_stream(self, stream, max_concurrency=None): """DEPRECATED: Download the contents of this file to a stream. This method is deprecated, use func:`readinto` instead. @@ -519,6 +520,6 @@ def download_to_stream(self, stream, max_concurrency=1): "download_to_stream is deprecated, use readinto instead", DeprecationWarning ) - self._max_concurrency = max_concurrency + self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY self.readinto(stream) return self.properties diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_file_client.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_file_client.py index ed032562a768..fd432be34b30 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_file_client.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_file_client.py @@ -41,6 +41,7 @@ get_source_access_conditions ) from ._shared.base_client import StorageAccountHostsMixin, parse_connection_str, parse_query +from ._shared.constants import DEFAULT_MAX_CONCURRENCY from ._shared.request_handlers import add_metadata_headers, get_length from ._shared.response_handlers import return_response_headers, process_storage_error from ._shared.uploads import IterStreamer, FileChunkUploader, upload_data_chunks @@ -594,7 +595,9 @@ def upload_file( """ metadata = kwargs.pop('metadata', None) content_settings = kwargs.pop('content_settings', None) - max_concurrency = kwargs.pop('max_concurrency', 1) + max_concurrency = kwargs.pop('max_concurrency', None) + if max_concurrency is None: + max_concurrency = DEFAULT_MAX_CONCURRENCY validate_content = kwargs.pop('validate_content', False) progress_hook = kwargs.pop('progress_hook', None) timeout = kwargs.pop('timeout', None) diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_file_client.pyi b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_file_client.pyi index 3fc1fb454d86..7136bf292c2a 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_file_client.pyi +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_file_client.pyi @@ -150,7 +150,7 @@ class ShareFileClient(StorageAccountHostsMixin): metadata: Optional[Dict[str, str]] = None, content_settings: Optional[ContentSettings] = None, validate_content: bool = False, - max_concurrency: int = 1, + max_concurrency: Optional[int] = None, lease: Optional[Union[ShareLeaseClient, str]] = None, progress_hook: Optional[Callable[[int, Optional[int]], None]] = None, encoding: str = "UTF-8", @@ -196,7 +196,7 @@ class ShareFileClient(StorageAccountHostsMixin): offset: Optional[int] = None, length: Optional[int] = None, *, - max_concurrency: int = 1, + max_concurrency: Optional[int] = None, validate_content: bool = False, lease: Optional[Union[ShareLeaseClient, str]] = None, progress_hook: Optional[Callable[[int, Optional[int]], None]] = None, diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/constants.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/constants.py index 50c760369faa..2bf865acf343 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/constants.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_shared/constants.py @@ -18,3 +18,5 @@ STORAGE_OAUTH_SCOPE = "https://storage.azure.com/.default" SERVICE_HOST_BASE = "core.windows.net" + +DEFAULT_MAX_CONCURRENCY = 1 diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_download_async.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_download_async.py index f37f15981f88..731e8c86bd92 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_download_async.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_download_async.py @@ -21,6 +21,7 @@ from .._download import _ChunkDownloader from .._shared.request_handlers import validate_and_format_range_headers from .._shared.response_handlers import parse_length_from_content_range, process_storage_error +from .._shared.constants import DEFAULT_MAX_CONCURRENCY if TYPE_CHECKING: from .._generated.aio.operations import FileOperations @@ -178,7 +179,7 @@ def __init__( start_range: Optional[int] = None, end_range: Optional[int] = None, validate_content: bool = None, # type: ignore [assignment] - max_concurrency: int = 1, + max_concurrency: Optional[int] = None, name: str = None, # type: ignore [assignment] path: str = None, # type: ignore [assignment] share: str = None, # type: ignore [assignment] @@ -194,7 +195,7 @@ def __init__( self._config = config self._start_range = start_range self._end_range = end_range - self._max_concurrency = max_concurrency + self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY self._encoding = encoding self._validate_content = validate_content self._progress_hook = kwargs.pop('progress_hook', None) @@ -358,7 +359,7 @@ async def readall(self) -> bytes: return data.decode(self._encoding) # type: ignore [return-value] return data - async def content_as_bytes(self, max_concurrency=1): + async def content_as_bytes(self, max_concurrency=None): """DEPRECATED: Download the contents of this file. This operation is blocking until all data is downloaded. @@ -374,10 +375,10 @@ async def content_as_bytes(self, max_concurrency=1): "content_as_bytes is deprecated, use readall instead", DeprecationWarning ) - self._max_concurrency = max_concurrency + self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY return await self.readall() - async def content_as_text(self, max_concurrency=1, encoding="UTF-8"): + async def content_as_text(self, max_concurrency=None, encoding="UTF-8"): """DEPRECATED: Download the contents of this file, and decode as text. This operation is blocking until all data is downloaded. @@ -395,7 +396,7 @@ async def content_as_text(self, max_concurrency=1, encoding="UTF-8"): "content_as_text is deprecated, use readall instead", DeprecationWarning ) - self._max_concurrency = max_concurrency + self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY self._encoding = encoding return await self.readall() @@ -480,7 +481,7 @@ async def readinto(self, stream: IO[bytes]) -> int: process_storage_error(error) return self.size - async def download_to_stream(self, stream, max_concurrency=1): + async def download_to_stream(self, stream, max_concurrency=None): """Download the contents of this file to a stream. This method is deprecated, use func:`readinto` instead. @@ -498,6 +499,6 @@ async def download_to_stream(self, stream, max_concurrency=1): "download_to_stream is deprecated, use readinto instead", DeprecationWarning ) - self._max_concurrency = max_concurrency + self._max_concurrency = max_concurrency if max_concurrency is not None else DEFAULT_MAX_CONCURRENCY await self.readinto(stream) return self.properties diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_file_client_async.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_file_client_async.py index 75d77c35cc1f..1ea648202ecc 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_file_client_async.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_file_client_async.py @@ -43,6 +43,7 @@ ) from .._shared.base_client import StorageAccountHostsMixin, parse_query from .._shared.base_client_async import AsyncStorageAccountHostsMixin, parse_connection_str +from .._shared.constants import DEFAULT_MAX_CONCURRENCY from .._shared.policies_async import ExponentialRetry from .._shared.request_handlers import add_metadata_headers, get_length from .._shared.response_handlers import process_storage_error, return_response_headers @@ -588,7 +589,9 @@ async def upload_file( """ metadata = kwargs.pop('metadata', None) content_settings = kwargs.pop('content_settings', None) - max_concurrency = kwargs.pop('max_concurrency', 1) + max_concurrency = kwargs.pop('max_concurrency', None) + if max_concurrency is None: + max_concurrency = DEFAULT_MAX_CONCURRENCY validate_content = kwargs.pop('validate_content', False) progress_hook = kwargs.pop('progress_hook', None) timeout = kwargs.pop('timeout', None) diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_file_client_async.pyi b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_file_client_async.pyi index 3218ca795360..45639c916d5d 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_file_client_async.pyi +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_file_client_async.pyi @@ -150,7 +150,7 @@ class ShareFileClient(AsyncStorageAccountHostsMixin, StorageAccountHostsMixin): metadata: Optional[Dict[str, str]] = None, content_settings: Optional[ContentSettings] = None, validate_content: bool = False, - max_concurrency: int = 1, + max_concurrency: Optional[int] = None, lease: Optional[Union[ShareLeaseClient, str]] = None, progress_hook: Optional[Callable[[int, Optional[int]], Awaitable[None]]] = None, encoding: str = "UTF-8", @@ -196,7 +196,7 @@ class ShareFileClient(AsyncStorageAccountHostsMixin, StorageAccountHostsMixin): offset: Optional[int] = None, length: Optional[int] = None, *, - max_concurrency: int = 1, + max_concurrency: Optional[int] = None, validate_content: bool = False, lease: Optional[Union[ShareLeaseClient, str]] = None, progress_hook: Optional[Callable[[int, Optional[int]], Awaitable[None]]] = None, diff --git a/sdk/storage/azure-storage-file-share/tests/test_file.py b/sdk/storage/azure-storage-file-share/tests/test_file.py index a4b666156b36..72104b1b7efd 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_file.py +++ b/sdk/storage/azure-storage-file-share/tests/test_file.py @@ -4015,4 +4015,49 @@ def test_download_file_no_decompress_chunks(self, **kwargs): result = file_client.download_file(decompress=False).readall() assert result == compressed_data + @FileSharePreparer() + @recorded_by_proxy + def test_upload_file_with_none_max_concurrency(self, **kwargs): + storage_account_name = kwargs.pop("storage_account_name") + storage_account_key = kwargs.pop("storage_account_key") + + self._setup(storage_account_name, storage_account_key) + file_name = self._get_file_reference() + file_client = ShareFileClient( + self.account_url(storage_account_name, "file"), + share_name=self.share_name, + file_path=file_name, + credential=storage_account_key.secret, + max_range_size=4 * 1024) + + data = b"hello world" + + # max_concurrency=None should not raise TypeError + file_client.upload_file(data, max_concurrency=None) + + self.assertFileEqual(file_client, data) + + @FileSharePreparer() + @recorded_by_proxy + def test_download_file_with_none_max_concurrency(self, **kwargs): + storage_account_name = kwargs.pop("storage_account_name") + storage_account_key = kwargs.pop("storage_account_key") + + self._setup(storage_account_name, storage_account_key) + file_name = self._get_file_reference() + file_client = ShareFileClient( + self.account_url(storage_account_name, "file"), + share_name=self.share_name, + file_path=file_name, + credential=storage_account_key.secret, + max_range_size=4 * 1024) + + data = b"hello world" + file_client.upload_file(data) + + # max_concurrency=None should not raise TypeError + content = file_client.download_file(max_concurrency=None).readall() + + assert content == data + # ------------------------------------------------------------------------------ diff --git a/sdk/storage/azure-storage-file-share/tests/test_file_async.py b/sdk/storage/azure-storage-file-share/tests/test_file_async.py index 0f57d9306974..0fd265019a4b 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_file_async.py +++ b/sdk/storage/azure-storage-file-share/tests/test_file_async.py @@ -4141,3 +4141,50 @@ async def test_download_file_no_decompress_chunks(self, **kwargs): result = await (await file_client.download_file(decompress=False)).readall() assert result == compressed_data + + @FileSharePreparer() + @recorded_by_proxy_async + async def test_upload_file_with_none_max_concurrency(self, **kwargs): + storage_account_name = kwargs.pop("storage_account_name") + storage_account_key = kwargs.pop("storage_account_key") + + self._setup(storage_account_name, storage_account_key) + await self._setup_share(storage_account_name, storage_account_key) + file_name = self._get_file_reference() + file_client = ShareFileClient( + self.account_url(storage_account_name, "file"), + share_name=self.share_name, + file_path=file_name, + credential=storage_account_key.secret, + max_range_size=4 * 1024) + + data = b"hello world" + + # max_concurrency=None should not raise TypeError + await file_client.upload_file(data, max_concurrency=None) + + await self.assertFileEqual(file_client, data) + + @FileSharePreparer() + @recorded_by_proxy_async + async def test_download_file_with_none_max_concurrency(self, **kwargs): + storage_account_name = kwargs.pop("storage_account_name") + storage_account_key = kwargs.pop("storage_account_key") + + self._setup(storage_account_name, storage_account_key) + await self._setup_share(storage_account_name, storage_account_key) + file_name = self._get_file_reference() + file_client = ShareFileClient( + self.account_url(storage_account_name, "file"), + share_name=self.share_name, + file_path=file_name, + credential=storage_account_key.secret, + max_range_size=4 * 1024) + + data = b"hello world" + await file_client.upload_file(data) + + # max_concurrency=None should not raise TypeError + content = await (await file_client.download_file(max_concurrency=None)).readall() + + assert content == data From 3a4db77480b62a6d44219ac3edf483593f1d724a Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Tue, 10 Mar 2026 17:18:57 -0700 Subject: [PATCH 2/2] Sync queue constants with other files --- .../azure/storage/queue/_shared/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/constants.py b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/constants.py index 50c760369faa..2bf865acf343 100644 --- a/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/constants.py +++ b/sdk/storage/azure-storage-queue/azure/storage/queue/_shared/constants.py @@ -18,3 +18,5 @@ STORAGE_OAUTH_SCOPE = "https://storage.azure.com/.default" SERVICE_HOST_BASE = "core.windows.net" + +DEFAULT_MAX_CONCURRENCY = 1