From 2fb8d3a468d9919908e2b0f44b5967b12dbafdb8 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 24 Jun 2026 10:20:06 -0400 Subject: [PATCH 1/7] Generated code --- .../azure/apiview-properties.json | 7 +- .../aio/operations/_file_operations.py | 16 +- .../fileshare/_generated/models/__init__.py | 10 + .../models/_azure_file_storage_enums.py | 10 +- .../_generated/models/_models_py3.py | 401 +++++++++++++++++- .../_generated/operations/_file_operations.py | 22 +- 6 files changed, 422 insertions(+), 44 deletions(-) diff --git a/sdk/storage/azure-storage-file-share/azure/apiview-properties.json b/sdk/storage/azure-storage-file-share/azure/apiview-properties.json index 779e93fd0e46..4bada19a7cd0 100644 --- a/sdk/storage/azure-storage-file-share/azure/apiview-properties.json +++ b/sdk/storage/azure-storage-file-share/azure/apiview-properties.json @@ -2,11 +2,14 @@ "CrossLanguagePackageId": null, "CrossLanguageDefinitionId": { "azure.storage.fileshare.models.AccessPolicy": null, + "azure.storage.fileshare.models.BlockDeviceItem": null, + "azure.storage.fileshare.models.CharDeviceItem": null, "azure.storage.fileshare.models.ClearRange": null, "azure.storage.fileshare.models.CopyFileSmbInfo": null, "azure.storage.fileshare.models.CorsRule": null, "azure.storage.fileshare.models.DestinationLeaseAccessConditions": null, "azure.storage.fileshare.models.DirectoryItem": null, + "azure.storage.fileshare.models.FifoItem": null, "azure.storage.fileshare.models.FileHTTPHeaders": null, "azure.storage.fileshare.models.FileItem": null, "azure.storage.fileshare.models.FileProperty": null, @@ -32,11 +35,13 @@ "azure.storage.fileshare.models.ShareStats": null, "azure.storage.fileshare.models.SignedIdentifier": null, "azure.storage.fileshare.models.SmbMultichannel": null, + "azure.storage.fileshare.models.SocketItem": null, "azure.storage.fileshare.models.SourceLeaseAccessConditions": null, "azure.storage.fileshare.models.SourceModifiedAccessConditions": null, "azure.storage.fileshare.models.StorageError": null, "azure.storage.fileshare.models.StorageServiceProperties": null, "azure.storage.fileshare.models.StringEncoded": null, + "azure.storage.fileshare.models.SymLinkItem": null, "azure.storage.fileshare.models.UserDelegationKey": null, "azure.storage.fileshare.models.ListSharesIncludeType": null, "azure.storage.fileshare.models.LeaseStatusType": null, @@ -44,11 +49,11 @@ "azure.storage.fileshare.models.LeaseDurationType": null, "azure.storage.fileshare.models.ShareRootSquash": null, "azure.storage.fileshare.models.ListFilesIncludeType": null, + "azure.storage.fileshare.models.NfsFileType": null, "azure.storage.fileshare.models.AccessRight": null, "azure.storage.fileshare.models.ShareTokenIntent": null, "azure.storage.fileshare.models.ShareAccessTier": null, "azure.storage.fileshare.models.FilePropertySemantics": null, - "azure.storage.fileshare.models.NfsFileType": null, "azure.storage.fileshare.models.StorageErrorCode": null, "azure.storage.fileshare.models.PermissionCopyModeType": null, "azure.storage.fileshare.models.FilePermissionFormat": null, diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/aio/operations/_file_operations.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/aio/operations/_file_operations.py index e779d40e5662..556e0da80568 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/aio/operations/_file_operations.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/aio/operations/_file_operations.py @@ -153,7 +153,8 @@ async def create( # pylint: disable=too-many-locals None. :type file_mode: str :param nfs_file_type: Optional, NFS only. Type of the file or directory. Known values are: - "Regular", "Directory", and "SymLink". Default value is None. + "Regular", "Directory", "SymLink", "BlockDevice", "CharacterDevice", "Socket", and "Fifo". + Default value is None. :type nfs_file_type: str or ~azure.storage.fileshare.models.NfsFileType :param content_md5: An MD5 hash of the content. This hash is used to verify the integrity of the data during transport. When the Content-MD5 header is specified, the File service compares @@ -1576,8 +1577,6 @@ async def get_range_list( timeout: Optional[int] = None, range: Optional[str] = None, support_rename: Optional[bool] = None, - marker: Optional[str] = None, - maxresults: Optional[int] = None, lease_access_conditions: Optional[_models.LeaseAccessConditions] = None, **kwargs: Any ) -> _models.ShareFileRangeList: @@ -1604,15 +1603,6 @@ async def get_range_list( operation will result in a failure with 409 (Conflict) response. The default value is false. Default value is None. :type support_rename: bool - :param marker: A string value that identifies the portion of the list to be returned with the - next list operation. The operation returns a marker value within the response body if the list - returned was not complete. The marker value may then be used in a subsequent call to request - the next set of list items. The marker value is opaque to the client. Default value is None. - :type marker: str - :param maxresults: Specifies the maximum number of entries to return. If the request does not - specify maxresults, or specifies a value greater than 5,000, the server will return up to 5,000 - items. Default value is None. - :type maxresults: int :param lease_access_conditions: Parameter group. Default value is None. :type lease_access_conditions: ~azure.storage.fileshare.models.LeaseAccessConditions :return: ShareFileRangeList or the result of cls(response) @@ -1646,8 +1636,6 @@ async def get_range_list( range=range, lease_id=_lease_id, support_rename=support_rename, - marker=marker, - maxresults=maxresults, allow_trailing_dot=self._config.allow_trailing_dot, file_request_intent=self._config.file_request_intent, comp=comp, diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/__init__.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/__init__.py index bb6fe56afaca..521754dca516 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/__init__.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/__init__.py @@ -15,11 +15,14 @@ from ._models_py3 import ( # type: ignore AccessPolicy, + BlockDeviceItem, + CharDeviceItem, ClearRange, CopyFileSmbInfo, CorsRule, DestinationLeaseAccessConditions, DirectoryItem, + FifoItem, FileHTTPHeaders, FileItem, FileProperty, @@ -45,11 +48,13 @@ ShareStats, SignedIdentifier, SmbMultichannel, + SocketItem, SourceLeaseAccessConditions, SourceModifiedAccessConditions, StorageError, StorageServiceProperties, StringEncoded, + SymLinkItem, UserDelegationKey, ) @@ -81,11 +86,14 @@ __all__ = [ "AccessPolicy", + "BlockDeviceItem", + "CharDeviceItem", "ClearRange", "CopyFileSmbInfo", "CorsRule", "DestinationLeaseAccessConditions", "DirectoryItem", + "FifoItem", "FileHTTPHeaders", "FileItem", "FileProperty", @@ -111,11 +119,13 @@ "ShareStats", "SignedIdentifier", "SmbMultichannel", + "SocketItem", "SourceLeaseAccessConditions", "SourceModifiedAccessConditions", "StorageError", "StorageServiceProperties", "StringEncoded", + "SymLinkItem", "UserDelegationKey", "AccessRight", "CopyStatusType", diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/_azure_file_storage_enums.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/_azure_file_storage_enums.py index 34c21387336a..aac23eca7616 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/_azure_file_storage_enums.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/_azure_file_storage_enums.py @@ -93,6 +93,10 @@ class ListFilesIncludeType(str, Enum, metaclass=CaseInsensitiveEnumMeta): ETAG = "Etag" ATTRIBUTES = "Attributes" PERMISSION_KEY = "PermissionKey" + PERMISSIONS = "Permissions" + LINK_COUNT = "LinkCount" + NFS_ATTRIBUTES = "NfsAttributes" + ALL = "All" class ListSharesIncludeType(str, Enum, metaclass=CaseInsensitiveEnumMeta): @@ -111,11 +115,15 @@ class ModeCopyMode(str, Enum, metaclass=CaseInsensitiveEnumMeta): class NfsFileType(str, Enum, metaclass=CaseInsensitiveEnumMeta): - """NfsFileType.""" + """Type of the file.""" REGULAR = "Regular" DIRECTORY = "Directory" SYM_LINK = "SymLink" + BLOCK_DEVICE = "BlockDevice" + CHARACTER_DEVICE = "CharacterDevice" + SOCKET = "Socket" + FIFO = "Fifo" class OwnerCopyMode(str, Enum, metaclass=CaseInsensitiveEnumMeta): diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/_models_py3.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/_models_py3.py index c20d2d586f38..096e8180582b 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/_models_py3.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/models/_models_py3.py @@ -55,6 +55,142 @@ def __init__( self.permission = permission +class BlockDeviceItem(_serialization.Model): + """A listed block device item. + + All required parameters must be populated in order to send to server. + + :ivar name: Required. + :vartype name: ~azure.storage.fileshare.models.StringEncoded + :ivar file_id: + :vartype file_id: str + :ivar properties: File properties. Required. + :vartype properties: ~azure.storage.fileshare.models.FileProperty + :ivar link_count: + :vartype link_count: int + :ivar device_major: + :vartype device_major: int + :ivar device_minor: + :vartype device_minor: int + """ + + _validation = { + "name": {"required": True}, + "properties": {"required": True}, + } + + _attribute_map = { + "name": {"key": "Name", "type": "StringEncoded"}, + "file_id": {"key": "FileId", "type": "str"}, + "properties": {"key": "Properties", "type": "FileProperty"}, + "link_count": {"key": "LinkCount", "type": "int"}, + "device_major": {"key": "DeviceMajor", "type": "int"}, + "device_minor": {"key": "DeviceMinor", "type": "int"}, + } + _xml_map = {"name": "BlockDevice"} + + def __init__( + self, + *, + name: "_models.StringEncoded", + properties: "_models.FileProperty", + file_id: Optional[str] = None, + link_count: Optional[int] = None, + device_major: Optional[int] = None, + device_minor: Optional[int] = None, + **kwargs: Any + ) -> None: + """ + :keyword name: Required. + :paramtype name: ~azure.storage.fileshare.models.StringEncoded + :keyword file_id: + :paramtype file_id: str + :keyword properties: File properties. Required. + :paramtype properties: ~azure.storage.fileshare.models.FileProperty + :keyword link_count: + :paramtype link_count: int + :keyword device_major: + :paramtype device_major: int + :keyword device_minor: + :paramtype device_minor: int + """ + super().__init__(**kwargs) + self.name = name + self.file_id = file_id + self.properties = properties + self.link_count = link_count + self.device_major = device_major + self.device_minor = device_minor + + +class CharDeviceItem(_serialization.Model): + """A listed character device item. + + All required parameters must be populated in order to send to server. + + :ivar name: Required. + :vartype name: ~azure.storage.fileshare.models.StringEncoded + :ivar file_id: + :vartype file_id: str + :ivar properties: File properties. Required. + :vartype properties: ~azure.storage.fileshare.models.FileProperty + :ivar link_count: + :vartype link_count: int + :ivar device_major: + :vartype device_major: int + :ivar device_minor: + :vartype device_minor: int + """ + + _validation = { + "name": {"required": True}, + "properties": {"required": True}, + } + + _attribute_map = { + "name": {"key": "Name", "type": "StringEncoded"}, + "file_id": {"key": "FileId", "type": "str"}, + "properties": {"key": "Properties", "type": "FileProperty"}, + "link_count": {"key": "LinkCount", "type": "int"}, + "device_major": {"key": "DeviceMajor", "type": "int"}, + "device_minor": {"key": "DeviceMinor", "type": "int"}, + } + _xml_map = {"name": "CharDevice"} + + def __init__( + self, + *, + name: "_models.StringEncoded", + properties: "_models.FileProperty", + file_id: Optional[str] = None, + link_count: Optional[int] = None, + device_major: Optional[int] = None, + device_minor: Optional[int] = None, + **kwargs: Any + ) -> None: + """ + :keyword name: Required. + :paramtype name: ~azure.storage.fileshare.models.StringEncoded + :keyword file_id: + :paramtype file_id: str + :keyword properties: File properties. Required. + :paramtype properties: ~azure.storage.fileshare.models.FileProperty + :keyword link_count: + :paramtype link_count: int + :keyword device_major: + :paramtype device_major: int + :keyword device_minor: + :paramtype device_minor: int + """ + super().__init__(**kwargs) + self.name = name + self.file_id = file_id + self.properties = properties + self.link_count = link_count + self.device_major = device_major + self.device_minor = device_minor + + class ClearRange(_serialization.Model): """ClearRange. @@ -309,6 +445,8 @@ class DirectoryItem(_serialization.Model): :vartype attributes: str :ivar permission_key: :vartype permission_key: str + :ivar link_count: + :vartype link_count: int """ _validation = { @@ -321,6 +459,7 @@ class DirectoryItem(_serialization.Model): "properties": {"key": "Properties", "type": "FileProperty"}, "attributes": {"key": "Attributes", "type": "str"}, "permission_key": {"key": "PermissionKey", "type": "str"}, + "link_count": {"key": "LinkCount", "type": "int"}, } _xml_map = {"name": "Directory"} @@ -332,6 +471,7 @@ def __init__( properties: Optional["_models.FileProperty"] = None, attributes: Optional[str] = None, permission_key: Optional[str] = None, + link_count: Optional[int] = None, **kwargs: Any ) -> None: """ @@ -345,6 +485,8 @@ def __init__( :paramtype attributes: str :keyword permission_key: :paramtype permission_key: str + :keyword link_count: + :paramtype link_count: int """ super().__init__(**kwargs) self.name = name @@ -352,6 +494,61 @@ def __init__( self.properties = properties self.attributes = attributes self.permission_key = permission_key + self.link_count = link_count + + +class FifoItem(_serialization.Model): + """A listed FIFO item. + + All required parameters must be populated in order to send to server. + + :ivar name: Required. + :vartype name: ~azure.storage.fileshare.models.StringEncoded + :ivar file_id: + :vartype file_id: str + :ivar properties: File properties. Required. + :vartype properties: ~azure.storage.fileshare.models.FileProperty + :ivar link_count: + :vartype link_count: int + """ + + _validation = { + "name": {"required": True}, + "properties": {"required": True}, + } + + _attribute_map = { + "name": {"key": "Name", "type": "StringEncoded"}, + "file_id": {"key": "FileId", "type": "str"}, + "properties": {"key": "Properties", "type": "FileProperty"}, + "link_count": {"key": "LinkCount", "type": "int"}, + } + _xml_map = {"name": "Fifo"} + + def __init__( + self, + *, + name: "_models.StringEncoded", + properties: "_models.FileProperty", + file_id: Optional[str] = None, + link_count: Optional[int] = None, + **kwargs: Any + ) -> None: + """ + :keyword name: Required. + :paramtype name: ~azure.storage.fileshare.models.StringEncoded + :keyword file_id: + :paramtype file_id: str + :keyword properties: File properties. Required. + :paramtype properties: ~azure.storage.fileshare.models.FileProperty + :keyword link_count: + :paramtype link_count: int + """ + super().__init__(**kwargs) + self.name = name + self.file_id = file_id + self.properties = properties + self.link_count = link_count class FileHTTPHeaders(_serialization.Model): @@ -434,6 +631,11 @@ class FileItem(_serialization.Model): :vartype attributes: str :ivar permission_key: :vartype permission_key: str + :ivar link_count: + :vartype link_count: int + :ivar file_type: Type of the file. Known values are: "Regular", "Directory", "SymLink", + "BlockDevice", "CharacterDevice", "Socket", and "Fifo". + :vartype file_type: str or ~azure.storage.fileshare.models.NfsFileType """ _validation = { @@ -447,6 +649,8 @@ class FileItem(_serialization.Model): "properties": {"key": "Properties", "type": "FileProperty"}, "attributes": {"key": "Attributes", "type": "str"}, "permission_key": {"key": "PermissionKey", "type": "str"}, + "link_count": {"key": "LinkCount", "type": "int"}, + "file_type": {"key": "FileType", "type": "str"}, } _xml_map = {"name": "File"} @@ -458,6 +662,8 @@ def __init__( file_id: Optional[str] = None, attributes: Optional[str] = None, permission_key: Optional[str] = None, + link_count: Optional[int] = None, + file_type: Optional[Union[str, "_models.NfsFileType"]] = None, **kwargs: Any ) -> None: """ @@ -471,6 +677,11 @@ def __init__( :paramtype attributes: str :keyword permission_key: :paramtype permission_key: str + :keyword link_count: + :paramtype link_count: int + :keyword file_type: Type of the file. Known values are: "Regular", "Directory", "SymLink", + "BlockDevice", "CharacterDevice", "Socket", and "Fifo". + :paramtype file_type: str or ~azure.storage.fileshare.models.NfsFileType """ super().__init__(**kwargs) self.name = name @@ -478,6 +689,8 @@ def __init__( self.properties = properties self.attributes = attributes self.permission_key = permission_key + self.link_count = link_count + self.file_type = file_type class FileProperty(_serialization.Model): @@ -502,6 +715,12 @@ class FileProperty(_serialization.Model): :vartype last_modified: ~datetime.datetime :ivar etag: :vartype etag: str + :ivar uid: + :vartype uid: str + :ivar gid: + :vartype gid: str + :ivar mode: + :vartype mode: str """ _validation = { @@ -516,6 +735,9 @@ class FileProperty(_serialization.Model): "change_time": {"key": "ChangeTime", "type": "iso-8601"}, "last_modified": {"key": "Last-Modified", "type": "rfc-1123"}, "etag": {"key": "Etag", "type": "str"}, + "uid": {"key": "Uid", "type": "str"}, + "gid": {"key": "Gid", "type": "str"}, + "mode": {"key": "Mode", "type": "str"}, } def __init__( @@ -528,6 +750,9 @@ def __init__( change_time: Optional[datetime.datetime] = None, last_modified: Optional[datetime.datetime] = None, etag: Optional[str] = None, + uid: Optional[str] = None, + gid: Optional[str] = None, + mode: Optional[str] = None, **kwargs: Any ) -> None: """ @@ -548,6 +773,12 @@ def __init__( :paramtype last_modified: ~datetime.datetime :keyword etag: :paramtype etag: str + :keyword uid: + :paramtype uid: str + :keyword gid: + :paramtype gid: str + :keyword mode: + :paramtype mode: str """ super().__init__(**kwargs) self.content_length = content_length @@ -557,6 +788,9 @@ def __init__( self.change_time = change_time self.last_modified = last_modified self.etag = etag + self.uid = uid + self.gid = gid + self.mode = mode class FileRange(_serialization.Model): @@ -602,6 +836,16 @@ class FilesAndDirectoriesListSegment(_serialization.Model): :vartype directory_items: list[~azure.storage.fileshare.models.DirectoryItem] :ivar file_items: Required. :vartype file_items: list[~azure.storage.fileshare.models.FileItem] + :ivar sym_link_items: + :vartype sym_link_items: list[~azure.storage.fileshare.models.SymLinkItem] + :ivar block_device_items: + :vartype block_device_items: list[~azure.storage.fileshare.models.BlockDeviceItem] + :ivar char_device_items: + :vartype char_device_items: list[~azure.storage.fileshare.models.CharDeviceItem] + :ivar fifo_items: + :vartype fifo_items: list[~azure.storage.fileshare.models.FifoItem] + :ivar socket_items: + :vartype socket_items: list[~azure.storage.fileshare.models.SocketItem] """ _validation = { @@ -612,21 +856,54 @@ class FilesAndDirectoriesListSegment(_serialization.Model): _attribute_map = { "directory_items": {"key": "DirectoryItems", "type": "[DirectoryItem]", "xml": {"itemsName": "Directory"}}, "file_items": {"key": "FileItems", "type": "[FileItem]", "xml": {"itemsName": "File"}}, + "sym_link_items": {"key": "SymLinkItems", "type": "[SymLinkItem]", "xml": {"itemsName": "SymLink"}}, + "block_device_items": { + "key": "BlockDeviceItems", + "type": "[BlockDeviceItem]", + "xml": {"itemsName": "BlockDevice"}, + }, + "char_device_items": {"key": "CharDeviceItems", "type": "[CharDeviceItem]", "xml": {"itemsName": "CharDevice"}}, + "fifo_items": {"key": "FifoItems", "type": "[FifoItem]", "xml": {"itemsName": "Fifo"}}, + "socket_items": {"key": "SocketItems", "type": "[SocketItem]", "xml": {"itemsName": "Socket"}}, } _xml_map = {"name": "Entries"} def __init__( - self, *, directory_items: list["_models.DirectoryItem"], file_items: list["_models.FileItem"], **kwargs: Any + self, + *, + directory_items: list["_models.DirectoryItem"], + file_items: list["_models.FileItem"], + sym_link_items: Optional[list["_models.SymLinkItem"]] = None, + block_device_items: Optional[list["_models.BlockDeviceItem"]] = None, + char_device_items: Optional[list["_models.CharDeviceItem"]] = None, + fifo_items: Optional[list["_models.FifoItem"]] = None, + socket_items: Optional[list["_models.SocketItem"]] = None, + **kwargs: Any ) -> None: """ :keyword directory_items: Required. :paramtype directory_items: list[~azure.storage.fileshare.models.DirectoryItem] :keyword file_items: Required. :paramtype file_items: list[~azure.storage.fileshare.models.FileItem] + :keyword sym_link_items: + :paramtype sym_link_items: list[~azure.storage.fileshare.models.SymLinkItem] + :keyword block_device_items: + :paramtype block_device_items: list[~azure.storage.fileshare.models.BlockDeviceItem] + :keyword char_device_items: + :paramtype char_device_items: list[~azure.storage.fileshare.models.CharDeviceItem] + :keyword fifo_items: + :paramtype fifo_items: list[~azure.storage.fileshare.models.FifoItem] + :keyword socket_items: + :paramtype socket_items: list[~azure.storage.fileshare.models.SocketItem] """ super().__init__(**kwargs) self.directory_items = directory_items self.file_items = file_items + self.sym_link_items = sym_link_items + self.block_device_items = block_device_items + self.char_device_items = char_device_items + self.fifo_items = fifo_items + self.socket_items = socket_items class HandleItem(_serialization.Model): @@ -1113,14 +1390,11 @@ class ShareFileRangeList(_serialization.Model): :vartype ranges: list[~azure.storage.fileshare.models.FileRange] :ivar clear_ranges: :vartype clear_ranges: list[~azure.storage.fileshare.models.ClearRange] - :ivar next_marker: - :vartype next_marker: str """ _attribute_map = { "ranges": {"key": "Ranges", "type": "[FileRange]", "xml": {"itemsName": "Range"}}, "clear_ranges": {"key": "ClearRanges", "type": "[ClearRange]", "xml": {"itemsName": "ClearRange"}}, - "next_marker": {"key": "NextMarker", "type": "str"}, } def __init__( @@ -1128,7 +1402,6 @@ def __init__( *, ranges: Optional[list["_models.FileRange"]] = None, clear_ranges: Optional[list["_models.ClearRange"]] = None, - next_marker: Optional[str] = None, **kwargs: Any ) -> None: """ @@ -1136,13 +1409,10 @@ def __init__( :paramtype ranges: list[~azure.storage.fileshare.models.FileRange] :keyword clear_ranges: :paramtype clear_ranges: list[~azure.storage.fileshare.models.ClearRange] - :keyword next_marker: - :paramtype next_marker: str """ super().__init__(**kwargs) self.ranges = ranges self.clear_ranges = clear_ranges - self.next_marker = next_marker class ShareItemInternal(_serialization.Model): @@ -1696,6 +1966,60 @@ def __init__(self, *, enabled: Optional[bool] = None, **kwargs: Any) -> None: self.enabled = enabled +class SocketItem(_serialization.Model): + """A listed socket item. + + All required parameters must be populated in order to send to server. + + :ivar name: Required. + :vartype name: ~azure.storage.fileshare.models.StringEncoded + :ivar file_id: + :vartype file_id: str + :ivar properties: File properties. Required. + :vartype properties: ~azure.storage.fileshare.models.FileProperty + :ivar link_count: + :vartype link_count: int + """ + + _validation = { + "name": {"required": True}, + "properties": {"required": True}, + } + + _attribute_map = { + "name": {"key": "Name", "type": "StringEncoded"}, + "file_id": {"key": "FileId", "type": "str"}, + "properties": {"key": "Properties", "type": "FileProperty"}, + "link_count": {"key": "LinkCount", "type": "int"}, + } + _xml_map = {"name": "Socket"} + + def __init__( + self, + *, + name: "_models.StringEncoded", + properties: "_models.FileProperty", + file_id: Optional[str] = None, + link_count: Optional[int] = None, + **kwargs: Any + ) -> None: + """ + :keyword name: Required. + :paramtype name: ~azure.storage.fileshare.models.StringEncoded + :keyword file_id: + :paramtype file_id: str + :keyword properties: File properties. Required. + :paramtype properties: ~azure.storage.fileshare.models.FileProperty + :keyword link_count: + :paramtype link_count: int + """ + super().__init__(**kwargs) + self.name = name + self.file_id = file_id + self.properties = properties + self.link_count = link_count + + class SourceLeaseAccessConditions(_serialization.Model): """Parameter group. @@ -1881,6 +2205,67 @@ def __init__(self, *, encoded: Optional[bool] = None, content: Optional[str] = N self.content = content +class SymLinkItem(_serialization.Model): + """A listed symbolic link item. + + All required parameters must be populated in order to send to server. + + :ivar name: Required. + :vartype name: ~azure.storage.fileshare.models.StringEncoded + :ivar file_id: + :vartype file_id: str + :ivar properties: File properties. Required. + :vartype properties: ~azure.storage.fileshare.models.FileProperty + :ivar link_count: + :vartype link_count: int + :ivar link_text: + :vartype link_text: str + """ + + _validation = { + "name": {"required": True}, + "properties": {"required": True}, + } + + _attribute_map = { + "name": {"key": "Name", "type": "StringEncoded"}, + "file_id": {"key": "FileId", "type": "str"}, + "properties": {"key": "Properties", "type": "FileProperty"}, + "link_count": {"key": "LinkCount", "type": "int"}, + "link_text": {"key": "LinkText", "type": "str"}, + } + _xml_map = {"name": "SymLink"} + + def __init__( + self, + *, + name: "_models.StringEncoded", + properties: "_models.FileProperty", + file_id: Optional[str] = None, + link_count: Optional[int] = None, + link_text: Optional[str] = None, + **kwargs: Any + ) -> None: + """ + :keyword name: Required. + :paramtype name: ~azure.storage.fileshare.models.StringEncoded + :keyword file_id: + :paramtype file_id: str + :keyword properties: File properties. Required. + :paramtype properties: ~azure.storage.fileshare.models.FileProperty + :keyword link_count: + :paramtype link_count: int + :keyword link_text: + :paramtype link_text: str + """ + super().__init__(**kwargs) + self.name = name + self.file_id = file_id + self.properties = properties + self.link_count = link_count + self.link_text = link_text + + class UserDelegationKey(_serialization.Model): """A user delegation key. diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/operations/_file_operations.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/operations/_file_operations.py index 8fb8e8632a55..dfe4889995ca 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/operations/_file_operations.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_generated/operations/_file_operations.py @@ -792,8 +792,6 @@ def build_get_range_list_request( range: Optional[str] = None, lease_id: Optional[str] = None, support_rename: Optional[bool] = None, - marker: Optional[str] = None, - maxresults: Optional[int] = None, allow_trailing_dot: Optional[bool] = None, file_request_intent: Optional[Union[str, _models.ShareTokenIntent]] = None, **kwargs: Any @@ -820,10 +818,6 @@ def build_get_range_list_request( _params["prevsharesnapshot"] = _SERIALIZER.query("prevsharesnapshot", prevsharesnapshot, "str") if timeout is not None: _params["timeout"] = _SERIALIZER.query("timeout", timeout, "int", minimum=0) - if marker is not None: - _params["marker"] = _SERIALIZER.query("marker", marker, "str") - if maxresults is not None: - _params["maxresults"] = _SERIALIZER.query("maxresults", maxresults, "int", minimum=1) # Construct headers _headers["x-ms-version"] = _SERIALIZER.header("version", version, "str") @@ -1419,7 +1413,8 @@ def create( # pylint: disable=inconsistent-return-statements,too-many-locals None. :type file_mode: str :param nfs_file_type: Optional, NFS only. Type of the file or directory. Known values are: - "Regular", "Directory", and "SymLink". Default value is None. + "Regular", "Directory", "SymLink", "BlockDevice", "CharacterDevice", "Socket", and "Fifo". + Default value is None. :type nfs_file_type: str or ~azure.storage.fileshare.models.NfsFileType :param content_md5: An MD5 hash of the content. This hash is used to verify the integrity of the data during transport. When the Content-MD5 header is specified, the File service compares @@ -2842,8 +2837,6 @@ def get_range_list( timeout: Optional[int] = None, range: Optional[str] = None, support_rename: Optional[bool] = None, - marker: Optional[str] = None, - maxresults: Optional[int] = None, lease_access_conditions: Optional[_models.LeaseAccessConditions] = None, **kwargs: Any ) -> _models.ShareFileRangeList: @@ -2870,15 +2863,6 @@ def get_range_list( operation will result in a failure with 409 (Conflict) response. The default value is false. Default value is None. :type support_rename: bool - :param marker: A string value that identifies the portion of the list to be returned with the - next list operation. The operation returns a marker value within the response body if the list - returned was not complete. The marker value may then be used in a subsequent call to request - the next set of list items. The marker value is opaque to the client. Default value is None. - :type marker: str - :param maxresults: Specifies the maximum number of entries to return. If the request does not - specify maxresults, or specifies a value greater than 5,000, the server will return up to 5,000 - items. Default value is None. - :type maxresults: int :param lease_access_conditions: Parameter group. Default value is None. :type lease_access_conditions: ~azure.storage.fileshare.models.LeaseAccessConditions :return: ShareFileRangeList or the result of cls(response) @@ -2912,8 +2896,6 @@ def get_range_list( range=range, lease_id=_lease_id, support_rename=support_rename, - marker=marker, - maxresults=maxresults, allow_trailing_dot=self._config.allow_trailing_dot, file_request_intent=self._config.file_request_intent, comp=comp, From d19a9b8df55cc53ee923e8faeec125a88a6a2a59 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 24 Jun 2026 12:48:23 -0400 Subject: [PATCH 2/7] WIP --- .../azure-storage-file-share/CHANGELOG.md | 3 + .../storage/fileshare/_directory_client.py | 5 +- .../azure/storage/fileshare/_models.py | 55 +++- .../azure/storage/fileshare/_share_client.py | 5 +- .../fileshare/aio/_directory_client_async.py | 5 +- .../azure/storage/fileshare/aio/_models.py | 25 +- .../fileshare/aio/_share_client_async.py | 5 +- .../tests/test_directory.py | 33 ++ .../tests/test_directory_async.py | 39 +++ .../tests/test_nfs.py | 286 ++++++++++++++++++ .../tests/test_nfs_async.py | 166 ++++++++++ 11 files changed, 617 insertions(+), 10 deletions(-) diff --git a/sdk/storage/azure-storage-file-share/CHANGELOG.md b/sdk/storage/azure-storage-file-share/CHANGELOG.md index ee89756394d2..63b0426d1452 100644 --- a/sdk/storage/azure-storage-file-share/CHANGELOG.md +++ b/sdk/storage/azure-storage-file-share/CHANGELOG.md @@ -7,6 +7,9 @@ `FileRange` and support continuation tokens and `results_per_page` for enumerating file ranges across multiple service responses. - Added a new `FileRange` model with `start`, `end`, and `cleared` properties. +- Added support for listing NFS files, directories, and special file types (symbolic links, block devices, +character devices, FIFOs, and sockets) via `list_directories_and_files`. The `include` keyword now accepts +the values `"Permissions"`, `"LinkCount"`, `"NfsAttributes"`, and `"All"`. ### Other Changes - Deprecated `ShareFileClient`'s `get_ranges` and `get_ranges_diff` APIs in favor of `list_ranges` and diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_directory_client.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_directory_client.py index 2428b94a85a0..eeace213d40e 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_directory_client.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_directory_client.py @@ -581,7 +581,10 @@ def list_directories_and_files( begin with the specified prefix. :keyword List[str] include: Include this parameter to specify one or more datasets to include in the response. - Possible str values are "timestamps", "Etag", "Attributes", "PermissionKey". + Possible str values are "timestamps", "Etag", "Attributes", "PermissionKey", + "Permissions", "LinkCount", "NfsAttributes", and "All". + The values "Permissions", "LinkCount", "NfsAttributes", and "All" apply to NFS shares, + with the exception of "All" which can also be used on SMB shares. .. versionadded:: 12.6.0 diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py index 4b942bc1c8c9..b6f3ba252637 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py @@ -991,6 +991,8 @@ class DirectoryProperties(DictMixin): """NFS only. The owning group of the directory.""" file_mode: Optional[str] = None """NFS only. The file mode of the directory.""" + link_count: Optional[int] = None + """NFS only. The number of hard links of the directory.""" nfs_file_type: Optional[Literal["Directory"]] = None """NFS only. The type of the directory.""" @@ -1024,6 +1026,7 @@ def __init__(self, **kwargs: Any) -> None: self.owner = kwargs.get("x-ms-owner") self.group = kwargs.get("x-ms-group") self.file_mode = kwargs.get("x-ms-mode") + self.link_count = kwargs.get("x-ms-link-count") self.nfs_file_type = kwargs.get("x-ms-file-file-type") @classmethod @@ -1039,6 +1042,11 @@ def _from_generated(cls, generated): props.change_time = generated.properties.change_time props.etag = generated.properties.etag props.permission_key = generated.permission_key + props.owner = generated.properties.uid + props.group = generated.properties.gid + props.file_mode = generated.properties.mode + props.link_count = generated.link_count + props.nfs_file_type = "Directory" if generated.properties.mode is not None else None return props @@ -1106,13 +1114,34 @@ def _extract_data_cb(self, get_next_return): self.service_endpoint = self._response.service_endpoint self.marker = self._response.marker self.results_per_page = self._response.max_results + segment = self._response.segment self.current_page = [ DirectoryProperties._from_generated(i) # pylint: disable = protected-access - for i in self._response.segment.directory_items + for i in segment.directory_items ] self.current_page.extend( FileProperties._from_generated(i) # pylint: disable = protected-access - for i in self._response.segment.file_items + for i in segment.file_items + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="SymLink") # pylint: disable = protected-access + for i in segment.sym_link_items or [] + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="BlockDevice") # pylint: disable = protected-access + for i in segment.block_device_items or [] + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="CharacterDevice") # pylint: disable = protected-access + for i in segment.char_device_items or [] + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="Fifo") # pylint: disable = protected-access + for i in segment.fifo_items or [] + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="Socket") # pylint: disable = protected-access + for i in segment.socket_items or [] ) return self._response.next_marker or None, self.current_page @@ -1337,8 +1366,14 @@ class FileProperties(DictMixin): """NFS only. The file mode of the file.""" link_count: Optional[int] = None """NFS only. The number of hard links of the file.""" - nfs_file_type: Optional[Literal["Regular"]] = None + nfs_file_type: Optional[Literal["Regular", "SymLink", "BlockDevice", "CharacterDevice", "Socket", "Fifo"]] = None """NFS only. The type of the file.""" + link_text: Optional[str] = None + """NFS only. The link text of the symbolic link. Only applicable to symbolic links.""" + device_major: Optional[int] = None + """NFS only. The major device number. Only applicable to block and character devices.""" + device_minor: Optional[int] = None + """NFS only. The minor device number. Only applicable to block and character devices.""" def __init__(self, **kwargs: Any) -> None: self.name = kwargs.get("name") # type: ignore [assignment] @@ -1382,9 +1417,12 @@ def __init__(self, **kwargs: Any) -> None: self.file_mode = kwargs.get("x-ms-mode") self.link_count = kwargs.get("x-ms-link-count") self.nfs_file_type = kwargs.get("x-ms-file-file-type") + self.link_text = None + self.device_major = None + self.device_minor = None @classmethod - def _from_generated(cls, generated): + def _from_generated(cls, generated, file_type=None): props = cls() props.name = unquote(generated.name.content) if generated.name.encoded else generated.name.content props.file_id = generated.file_id @@ -1396,7 +1434,16 @@ def _from_generated(cls, generated): props.last_write_time = generated.properties.last_write_time props.change_time = generated.properties.change_time props.size = generated.properties.content_length + props.content_length = generated.properties.content_length props.permission_key = generated.permission_key + props.owner = generated.properties.uid + props.group = generated.properties.gid + props.file_mode = generated.properties.mode + props.link_count = generated.link_count + props.nfs_file_type = generated.file_type or file_type + props.link_text = generated.link_text + props.device_major = generated.device_major + props.device_minor = generated.device_minor return props diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_share_client.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_share_client.py index 677d7eccb4e0..3d24eff7a021 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_share_client.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_share_client.py @@ -906,7 +906,10 @@ def list_directories_and_files( this generator will begin returning results from this point. :keyword List[str] include: Include this parameter to specify one or more datasets to include in the response. - Possible str values are "timestamps", "Etag", "Attributes", "PermissionKey". + Possible str values are "timestamps", "Etag", "Attributes", "PermissionKey", + "Permissions", "LinkCount", "NfsAttributes", and "All". + The values "Permissions", "LinkCount", "NfsAttributes", and "All" apply to NFS shares, + with the exception of "All" which can also be used on SMB shares. .. versionadded:: 12.6.0 diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_directory_client_async.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_directory_client_async.py index d117042e73f0..4f25f9e78da6 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_directory_client_async.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_directory_client_async.py @@ -591,7 +591,10 @@ def list_directories_and_files( begin with the specified prefix. :keyword List[str] include: Include this parameter to specify one or more datasets to include in the response. - Possible str values are "timestamps", "Etag", "Attributes", "PermissionKey". + Possible str values are "timestamps", "Etag", "Attributes", "PermissionKey", + "Permissions", "LinkCount", "NfsAttributes", and "All". + The values "Permissions", "LinkCount", "NfsAttributes", and "All" apply to NFS shares, + with the exception of "All" which can also be used on SMB shares. .. versionadded:: 12.6.0 diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_models.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_models.py index e65384d26e71..28de23d95cde 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_models.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_models.py @@ -201,13 +201,34 @@ async def _extract_data_cb(self, get_next_return): self.service_endpoint = self._response.service_endpoint self.marker = self._response.marker self.results_per_page = self._response.max_results + segment = self._response.segment self.current_page = [ DirectoryProperties._from_generated(i) # pylint: disable = protected-access - for i in self._response.segment.directory_items + for i in segment.directory_items ] self.current_page.extend( FileProperties._from_generated(i) # pylint: disable = protected-access - for i in self._response.segment.file_items + for i in segment.file_items + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="SymLink") # pylint: disable = protected-access + for i in segment.sym_link_items or [] + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="BlockDevice") # pylint: disable = protected-access + for i in segment.block_device_items or [] + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="CharacterDevice") # pylint: disable = protected-access + for i in segment.char_device_items or [] + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="Fifo") # pylint: disable = protected-access + for i in segment.fifo_items or [] + ) + self.current_page.extend( + FileProperties._from_generated(i, file_type="Socket") # pylint: disable = protected-access + for i in segment.socket_items or [] ) return self._response.next_marker or None, self.current_page diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_share_client_async.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_share_client_async.py index 26d7867e6036..a3b923fde202 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_share_client_async.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_share_client_async.py @@ -910,7 +910,10 @@ def list_directories_and_files( this generator will begin returning results from this point. :keyword List[str] include: Include this parameter to specify one or more datasets to include in the response. - Possible str values are "timestamps", "Etag", "Attributes", "PermissionKey". + Possible str values are "timestamps", "Etag", "Attributes", "PermissionKey", + "Permissions", "LinkCount", "NfsAttributes", and "All". + The values "Permissions", "LinkCount", "NfsAttributes", and "All" apply to NFS shares, + with the exception of "All" which can also be used on SMB shares. .. versionadded:: 12.6.0 diff --git a/sdk/storage/azure-storage-file-share/tests/test_directory.py b/sdk/storage/azure-storage-file-share/tests/test_directory.py index 445c16dedd17..de11b4a8816d 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_directory.py +++ b/sdk/storage/azure-storage-file-share/tests/test_directory.py @@ -917,6 +917,39 @@ def test_list_subdirectories_and_files_include_other_data(self, **kwargs): except: pass + @FileSharePreparer() + @recorded_by_proxy + def test_list_subdirectories_and_files_include_all(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) + share_client = self.fsc.get_share_client(self.share_name) + directory = share_client.create_directory("dir1") + directory.create_subdirectory("subdir1") + directory.upload_file("file1", "data1") + + # Act: "All" is supported on SMB shares and includes the SMB datasets + list_dir = list(directory.list_directories_and_files(include=["All"])) + + # Assert: SMB properties are populated, NFS-only properties are not + assert len(list_dir) == 2 + for props in list_dir: + assert props.etag is not None + assert props.file_attributes is not None + assert props.permission_key is not None + assert props.creation_time is not None + assert props.last_write_time is not None + assert props.change_time is not None + assert props.file_id is not None + assert props.owner is None + assert props.group is None + assert props.file_mode is None + try: + share_client.delete_share() + except: + pass + @FileSharePreparer() @recorded_by_proxy def test_list_subdirectories_and_files_include_extended_info(self, **kwargs): diff --git a/sdk/storage/azure-storage-file-share/tests/test_directory_async.py b/sdk/storage/azure-storage-file-share/tests/test_directory_async.py index 1a4dcaed7b8b..e0aaf17ad9da 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_directory_async.py +++ b/sdk/storage/azure-storage-file-share/tests/test_directory_async.py @@ -984,6 +984,45 @@ async def test_list_subdirectories_and_files_include_other_data_async(self, **kw except: pass + @FileSharePreparer() + @recorded_by_proxy_async + async def test_list_subdirectories_and_files_include_all_async(self, **kwargs): + storage_account_name = kwargs.pop("storage_account_name") + storage_account_key = kwargs.pop("storage_account_key") + + # Arrange + await self._setup(storage_account_name, storage_account_key) + share_client = self.fsc.get_share_client(self.share_name) + directory = await share_client.create_directory("dir1") + await asyncio.gather( + directory.create_subdirectory("subdir1"), + directory.upload_file("file1", "data1"), + ) + + # Act: "All" is supported on SMB shares and includes the SMB datasets + list_dir = [] + async for d in directory.list_directories_and_files(include=["All"]): + list_dir.append(d) + + # Assert: SMB properties are populated, NFS-only properties are not + assert len(list_dir) == 2 + for props in list_dir: + assert props.etag is not None + assert props.file_attributes is not None + assert props.permission_key is not None + assert props.creation_time is not None + assert props.last_write_time is not None + assert props.change_time is not None + assert props.file_id is not None + assert props.owner is None + assert props.group is None + assert props.file_mode is None + + try: + await share_client.delete_share() + except: + pass + @FileSharePreparer() @recorded_by_proxy_async async def test_list_subdirectories_and_files_include_extended_info(self, **kwargs): diff --git a/sdk/storage/azure-storage-file-share/tests/test_nfs.py b/sdk/storage/azure-storage-file-share/tests/test_nfs.py index e989539534ba..8219090795ab 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_nfs.py +++ b/sdk/storage/azure-storage-file-share/tests/test_nfs.py @@ -14,6 +14,7 @@ from settings.testcase import FileSharePreparer from azure.core.exceptions import ResourceNotFoundError +from azure.core.pipeline.transport import HttpTransport, RequestsTransportResponse # pylint: disable=no-name-in-module from azure.storage.fileshare import ( ContentSettings, DirectoryProperties, @@ -22,11 +23,175 @@ ShareFileClient, ShareServiceClient, ) +from requests.structures import CaseInsensitiveDict + +from test_helpers import MockHttpClientResponse TEST_INTENT = "backup" TEST_FILE_PREFIX = "file" TEST_DIRECTORY_PREFIX = "directory" +# A canned "List Files and Directories" response containing every NFS item type. It is not possible to +# create block devices, character devices, FIFOs, or sockets through the REST interface, so a mocked +# response is used to verify that the SDK can parse all of them. +LIST_FILES_AND_DIRECTORIES_NFS_XML = b""" + + + 5000 + + + subdir + 12682206919419625485 + + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + Mon, 01 Jan 2024 00:00:00 GMT + "0x8DC0000000000A0" + 0 + 0 + 0755 + + 2 + + + regular.txt + 11111111111111111111 + + 80 + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + Mon, 01 Jan 2024 00:00:00 GMT + "0x8DC0000000000B0" + 1000 + 1000 + 0644 + + 2 + + + symlink.txt + 22222222222222222222 + + 0 + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + Mon, 01 Jan 2024 00:00:00 GMT + "0x8DC0000000000C0" + 1000 + 1000 + 0777 + + 1 + /mnt/s2/dir2/regular.txt + + + block_device + 10952824662509355033 + + 0 + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + Mon, 01 Jan 2024 00:00:00 GMT + "0x8DC0000000000D0" + 0 + 0 + 0640 + + 1 + 8 + 0 + + + char_device + 33333333333333333333 + + 0 + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + Mon, 01 Jan 2024 00:00:00 GMT + "0x8DC0000000000E0" + 0 + 0 + 0644 + + 1 + 1 + 7 + + + fifo_pipe + 44444444444444444444 + + 0 + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + Mon, 01 Jan 2024 00:00:00 GMT + "0x8DC0000000000F0" + 1000 + 1000 + 0644 + + 1 + + + unix_socket + 55555555555555555555 + + 0 + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + 2024-01-01T00:00:00.0000000Z + Mon, 01 Jan 2024 00:00:00 GMT + "0x8DC00000000010A" + 0 + 0 + 0755 + + 1 + + + +""" + + +class MockListTransport(HttpTransport): + """A transport that returns a canned XML body for the list operation (an HTTP GET).""" + + def __init__(self, body: bytes) -> None: + self._body = body + + def send(self, request, **kwargs) -> RequestsTransportResponse: + return RequestsTransportResponse( + request=request, + requests_response=MockHttpClientResponse( + request.url, + self._body, + CaseInsensitiveDict( + {"Content-Type": "application/xml", "Content-Length": str(len(self._body))} + ), + ), + ) + + def open(self) -> None: + pass + + def close(self) -> None: + pass + + def __enter__(self) -> "MockListTransport": + return self + + def __exit__(self, *args: Any) -> None: + pass + + class TestStorageFileNFS(StorageRecordedTestCase): @@ -319,3 +484,124 @@ def test_create_and_get_symlink_error(self, **kwargs): with pytest.raises(ResourceNotFoundError) as e: symbolic_link_file_client.get_symlink() assert "ParentNotFound" in e.value.args[0] + + @FileSharePreparer() + @recorded_by_proxy + def test_list_directories_and_files(self, **kwargs: Any): + premium_storage_file_account_name = kwargs.pop("premium_storage_file_account_name") + + self._setup(premium_storage_file_account_name) + + share_client = self.fsc.get_share_client(self.share_name) + directory_name = self._get_directory_name() + directory_client = share_client.create_directory( + directory_name, owner="345", group="123", file_mode="0755" + ) + + file_name = self._get_file_name("file1") + file_client = directory_client.get_file_client(file_name) + file_client.create_file(size=1024, owner="345", group="123", file_mode="0644") + + symlink_name = self._get_file_name("file2") + symlink_client = directory_client.get_file_client(symlink_name) + target = f"{directory_name}/{file_name}" + symlink_client.create_symlink(target=target, owner="345", group="123") + + # Act + items = list( + directory_client.list_directories_and_files( + include=["Timestamps", "ETag", "Permissions", "LinkCount", "NfsAttributes"] + ) + ) + items_by_name = {item.name: item for item in items} + + # Assert: file + file_props = items_by_name[file_name] + assert isinstance(file_props, FileProperties) + assert file_props.owner == "345" + assert file_props.group == "123" + assert file_props.file_mode == "0644" + assert file_props.nfs_file_type is None + assert file_props.link_count == 1 + assert file_props.file_attributes is None + assert file_props.permission_key is None + assert file_props.etag is not None + + # Assert: symbolic link + symlink_props = items_by_name[symlink_name] + assert isinstance(symlink_props, FileProperties) + assert symlink_props.owner == "345" + assert symlink_props.group == "123" + assert symlink_props.nfs_file_type == "SymLink" + assert symlink_props.link_count == 1 + + def test_list_directories_and_files_special_types_mock(self): + directory_client = ShareDirectoryClient( + "https://fakeaccount.file.core.windows.net", + share_name="share", + directory_path="", + credential="ZmFrZWtleWZha2VrZXlmYWtla2V5", + transport=MockListTransport(LIST_FILES_AND_DIRECTORIES_NFS_XML), + ) + + items = list(directory_client.list_directories_and_files()) + items_by_name = {item.name: item for item in items} + assert len(items) == 7 + + directory = items_by_name["subdir"] + assert isinstance(directory, DirectoryProperties) + assert directory.is_directory is True + assert directory.owner == "0" + assert directory.group == "0" + assert directory.file_mode == "0755" + assert directory.link_count == 2 + assert directory.nfs_file_type == "Directory" + + regular = items_by_name["regular.txt"] + assert isinstance(regular, FileProperties) + assert regular.is_directory is False + assert regular.size == 80 + assert regular.owner == "1000" + assert regular.group == "1000" + assert regular.file_mode == "0644" + assert regular.link_count == 2 + assert regular.nfs_file_type is None + + symlink = items_by_name["symlink.txt"] + assert isinstance(symlink, FileProperties) + assert symlink.nfs_file_type == "SymLink" + assert symlink.file_mode == "0777" + assert symlink.link_count == 1 + assert symlink.link_text == "/mnt/s2/dir2/regular.txt" + + block_device = items_by_name["block_device"] + assert isinstance(block_device, FileProperties) + assert block_device.nfs_file_type == "BlockDevice" + assert block_device.owner == "0" + assert block_device.group == "0" + assert block_device.file_mode == "0640" + assert block_device.link_count == 1 + assert block_device.device_major == 8 + assert block_device.device_minor == 0 + + char_device = items_by_name["char_device"] + assert isinstance(char_device, FileProperties) + assert char_device.nfs_file_type == "CharacterDevice" + assert char_device.file_mode == "0644" + assert char_device.device_major == 1 + assert char_device.device_minor == 7 + + fifo = items_by_name["fifo_pipe"] + assert isinstance(fifo, FileProperties) + assert fifo.nfs_file_type == "Fifo" + assert fifo.owner == "1000" + assert fifo.group == "1000" + assert fifo.file_mode == "0644" + assert fifo.link_count == 1 + + socket = items_by_name["unix_socket"] + assert isinstance(socket, FileProperties) + assert socket.nfs_file_type == "Socket" + assert socket.file_mode == "0755" + assert socket.link_count == 1 + diff --git a/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py b/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py index 768bedbb2e52..2b7869242bfd 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py +++ b/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py @@ -14,6 +14,10 @@ from settings.testcase import FileSharePreparer from azure.core.exceptions import ResourceNotFoundError +from azure.core.pipeline.transport import ( # pylint: disable=no-name-in-module + AioHttpTransportResponse, + AsyncHttpTransport, +) from azure.storage.fileshare import ( ContentSettings, DirectoryProperties, @@ -23,11 +27,50 @@ from azure.storage.fileshare.aio import ShareDirectoryClient, ShareFileClient from azure.storage.fileshare.aio import ShareServiceClient as AsyncShareServiceClient +from requests.structures import CaseInsensitiveDict + +from test_helpers_async import MockAioHttpClientResponse +from test_nfs import LIST_FILES_AND_DIRECTORIES_NFS_XML + TEST_INTENT = "backup" TEST_FILE_PREFIX = "file" TEST_DIRECTORY_PREFIX = "directory" +class MockListTransport(AsyncHttpTransport): + """A transport that returns a canned XML body for the list operation (an HTTP GET).""" + + def __init__(self, body: bytes) -> None: + self._body = body + + async def send(self, request, **kwargs) -> AioHttpTransportResponse: + rest_response = AioHttpTransportResponse( + request=request, + aiohttp_response=MockAioHttpClientResponse( + request.url, + self._body, + CaseInsensitiveDict( + {"Content-Type": "application/xml", "Content-Length": str(len(self._body))} + ), + ), + decompress=False, + ) + await rest_response.load_body() + return rest_response + + async def open(self) -> None: + pass + + async def close(self) -> None: + pass + + async def __aenter__(self) -> "MockListTransport": + return self + + async def __aexit__(self, *args: Any) -> None: + pass + + class TestStorageFileNFSAsync(AsyncStorageRecordedTestCase): fsc: AsyncShareServiceClient = None @@ -330,3 +373,126 @@ async def test_create_and_get_symlink_error(self, **kwargs): with pytest.raises(ResourceNotFoundError) as e: await symbolic_link_file_client.get_symlink() assert "ParentNotFound" in e.value.args[0] + + @FileSharePreparer() + @recorded_by_proxy_async + async def test_list_directories_and_files(self, **kwargs: Any): + premium_storage_file_account_name = kwargs.pop("premium_storage_file_account_name") + + await self._setup(premium_storage_file_account_name) + + share_client = self.fsc.get_share_client(self.share_name) + directory_name = self._get_directory_name() + directory_client = await share_client.create_directory( + directory_name, owner="345", group="123", file_mode="0755" + ) + + file_name = self._get_file_name("file1") + file_client = directory_client.get_file_client(file_name) + await file_client.create_file(size=1024, owner="345", group="123", file_mode="0644") + + symlink_name = self._get_file_name("file2") + symlink_client = directory_client.get_file_client(symlink_name) + target = f"{directory_name}/{file_name}" + await symlink_client.create_symlink(target=target, owner="345", group="123") + + # Act + items = [] + async for item in directory_client.list_directories_and_files( + include=["Timestamps", "ETag", "Permissions", "LinkCount", "NfsAttributes"] + ): + items.append(item) + items_by_name = {item.name: item for item in items} + + # Assert: file + file_props = items_by_name[file_name] + assert isinstance(file_props, FileProperties) + assert file_props.owner == "345" + assert file_props.group == "123" + assert file_props.file_mode == "0644" + assert file_props.nfs_file_type is None + assert file_props.link_count == 1 + assert file_props.file_attributes is None + assert file_props.permission_key is None + assert file_props.etag is not None + + # Assert: symbolic link + symlink_props = items_by_name[symlink_name] + assert isinstance(symlink_props, FileProperties) + assert symlink_props.owner == "345" + assert symlink_props.group == "123" + assert symlink_props.nfs_file_type == "SymLink" + assert symlink_props.link_count == 1 + + async def test_list_directories_and_files_special_types_mock(self): + directory_client = ShareDirectoryClient( + "https://fakeaccount.file.core.windows.net", + share_name="share", + directory_path="", + credential="ZmFrZWtleWZha2VrZXlmYWtla2V5", + transport=MockListTransport(LIST_FILES_AND_DIRECTORIES_NFS_XML), + ) + + items = [] + async for item in directory_client.list_directories_and_files(): + items.append(item) + items_by_name = {item.name: item for item in items} + assert len(items) == 7 + + directory = items_by_name["subdir"] + assert isinstance(directory, DirectoryProperties) + assert directory.is_directory is True + assert directory.owner == "0" + assert directory.group == "0" + assert directory.file_mode == "0755" + assert directory.link_count == 2 + assert directory.nfs_file_type == "Directory" + + regular = items_by_name["regular.txt"] + assert isinstance(regular, FileProperties) + assert regular.is_directory is False + assert regular.size == 80 + assert regular.owner == "1000" + assert regular.group == "1000" + assert regular.file_mode == "0644" + assert regular.link_count == 2 + assert regular.nfs_file_type is None + + symlink = items_by_name["symlink.txt"] + assert isinstance(symlink, FileProperties) + assert symlink.nfs_file_type == "SymLink" + assert symlink.file_mode == "0777" + assert symlink.link_count == 1 + assert symlink.link_text == "/mnt/s2/dir2/regular.txt" + + block_device = items_by_name["block_device"] + assert isinstance(block_device, FileProperties) + assert block_device.nfs_file_type == "BlockDevice" + assert block_device.owner == "0" + assert block_device.group == "0" + assert block_device.file_mode == "0640" + assert block_device.link_count == 1 + assert block_device.device_major == 8 + assert block_device.device_minor == 0 + + char_device = items_by_name["char_device"] + assert isinstance(char_device, FileProperties) + assert char_device.nfs_file_type == "CharacterDevice" + assert char_device.file_mode == "0644" + assert char_device.device_major == 1 + assert char_device.device_minor == 7 + + fifo = items_by_name["fifo_pipe"] + assert isinstance(fifo, FileProperties) + assert fifo.nfs_file_type == "Fifo" + assert fifo.owner == "1000" + assert fifo.group == "1000" + assert fifo.file_mode == "0644" + assert fifo.link_count == 1 + + socket = items_by_name["unix_socket"] + assert isinstance(socket, FileProperties) + assert socket.nfs_file_type == "Socket" + assert socket.file_mode == "0755" + assert socket.link_count == 1 + From 916d52ea1876850d21d8a0e52e049fe9d75596d2 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 24 Jun 2026 12:50:50 -0400 Subject: [PATCH 3/7] Attributes need to be read defensively as some are not present --- .../azure/storage/fileshare/_models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py index b6f3ba252637..e2ea4c6f0c48 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py @@ -1427,7 +1427,7 @@ def _from_generated(cls, generated, file_type=None): props.name = unquote(generated.name.content) if generated.name.encoded else generated.name.content props.file_id = generated.file_id props.etag = generated.properties.etag - props.file_attributes = generated.attributes + props.file_attributes = getattr(generated, "attributes", None) props.last_modified = generated.properties.last_modified props.creation_time = generated.properties.creation_time props.last_access_time = generated.properties.last_access_time @@ -1435,15 +1435,15 @@ def _from_generated(cls, generated, file_type=None): props.change_time = generated.properties.change_time props.size = generated.properties.content_length props.content_length = generated.properties.content_length - props.permission_key = generated.permission_key + props.permission_key = getattr(generated, "permission_key", None) props.owner = generated.properties.uid props.group = generated.properties.gid props.file_mode = generated.properties.mode props.link_count = generated.link_count - props.nfs_file_type = generated.file_type or file_type - props.link_text = generated.link_text - props.device_major = generated.device_major - props.device_minor = generated.device_minor + props.nfs_file_type = getattr(generated, "file_type", None) or file_type + props.link_text = getattr(generated, "link_text", None) + props.device_major = getattr(generated, "device_major", None) + props.device_minor = getattr(generated, "device_minor", None) return props From 0c1ad86d2f40de4b6191ee47814a333ebcde38dd Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 24 Jun 2026 12:57:49 -0400 Subject: [PATCH 4/7] Cleanup --- sdk/storage/azure-storage-file-share/tests/test_nfs.py | 8 +++----- .../azure-storage-file-share/tests/test_nfs_async.py | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/sdk/storage/azure-storage-file-share/tests/test_nfs.py b/sdk/storage/azure-storage-file-share/tests/test_nfs.py index 8219090795ab..5fef053afa30 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_nfs.py +++ b/sdk/storage/azure-storage-file-share/tests/test_nfs.py @@ -13,7 +13,7 @@ from devtools_testutils.storage import StorageRecordedTestCase from settings.testcase import FileSharePreparer -from azure.core.exceptions import ResourceNotFoundError +from azure.core.exceptions import HttpResponseError, ResourceExistsError, ResourceNotFoundError from azure.core.pipeline.transport import HttpTransport, RequestsTransportResponse # pylint: disable=no-name-in-module from azure.storage.fileshare import ( ContentSettings, @@ -207,14 +207,14 @@ def _setup(self, storage_account_name: str): if self.is_live: try: self.fsc.create_share(self.share_name, protocols="NFS") - except: + except ResourceExistsError: pass def teardown_method(self): if self.is_live and self.fsc: try: self.fsc.delete_share(self.share_name) - except: + except HttpResponseError: pass # --Helpers---------------------------------------------------------- @@ -415,7 +415,6 @@ def test_create_hardlink_error(self, **kwargs: Any): directory_name = self._get_directory_name() directory_client = share_client.get_directory_client(directory_name) source_file_name = self._get_file_name("file1") - source_file_client = directory_client.get_file_client(source_file_name) hard_link_file_name = self._get_file_name("file2") hard_link_file_client = directory_client.get_file_client(hard_link_file_name) @@ -472,7 +471,6 @@ def test_create_and_get_symlink_error(self, **kwargs): directory_name = self._get_directory_name() directory_client = share_client.get_directory_client(directory_name) source_file_name = self._get_file_name("file1") - source_file_client = directory_client.get_file_client(source_file_name) symbolic_link_file_name = self._get_file_name("file2") symbolic_link_file_client = directory_client.get_file_client(symbolic_link_file_name) target = f"{directory_name}/{source_file_name}" diff --git a/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py b/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py index 2b7869242bfd..c5b8cebaa6ba 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py +++ b/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py @@ -13,7 +13,7 @@ from devtools_testutils.storage.aio import AsyncStorageRecordedTestCase from settings.testcase import FileSharePreparer -from azure.core.exceptions import ResourceNotFoundError +from azure.core.exceptions import HttpResponseError, ResourceExistsError, ResourceNotFoundError from azure.core.pipeline.transport import ( # pylint: disable=no-name-in-module AioHttpTransportResponse, AsyncHttpTransport, @@ -89,7 +89,7 @@ async def _setup(self, storage_account_name: str): if self.is_live: try: await fsc.create_share(self.share_name, protocols="NFS") - except: + except ResourceExistsError: pass def teardown_method(self): @@ -101,7 +101,7 @@ def teardown_method(self): token_intent=TEST_INTENT, ) fsc.delete_share(self.share_name) - except: + except HttpResponseError: pass # --Helpers---------------------------------------------------------- @@ -302,7 +302,6 @@ async def test_create_hardlink_error(self, **kwargs: Any): directory_name = self._get_directory_name() directory_client = share_client.get_directory_client(directory_name) source_file_name = self._get_file_name("file1") - source_file_client = directory_client.get_file_client(source_file_name) hard_link_file_name = self._get_file_name("file2") hard_link_file_client = directory_client.get_file_client(hard_link_file_name) @@ -361,7 +360,6 @@ async def test_create_and_get_symlink_error(self, **kwargs): directory_name = self._get_directory_name() directory_client = share_client.get_directory_client(directory_name) source_file_name = self._get_file_name("file1") - source_file_client = directory_client.get_file_client(source_file_name) symbolic_link_file_name = self._get_file_name("file2") symbolic_link_file_client = directory_client.get_file_client(symbolic_link_file_name) target = f"{directory_name}/{source_file_name}" From fb7d0971186a1e917c06611ee1c5bee526e6e6e2 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 24 Jun 2026 12:58:22 -0400 Subject: [PATCH 5/7] Black --- .../azure/storage/fileshare/_models.py | 3 +-- .../azure/storage/fileshare/aio/_models.py | 3 +-- sdk/storage/azure-storage-file-share/tests/test_nfs.py | 10 ++-------- .../azure-storage-file-share/tests/test_nfs_async.py | 5 +---- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py index e2ea4c6f0c48..95dd3ded352e 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/_models.py @@ -1120,8 +1120,7 @@ def _extract_data_cb(self, get_next_return): for i in segment.directory_items ] self.current_page.extend( - FileProperties._from_generated(i) # pylint: disable = protected-access - for i in segment.file_items + FileProperties._from_generated(i) for i in segment.file_items # pylint: disable = protected-access ) self.current_page.extend( FileProperties._from_generated(i, file_type="SymLink") # pylint: disable = protected-access diff --git a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_models.py b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_models.py index 28de23d95cde..1dce336f0780 100644 --- a/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_models.py +++ b/sdk/storage/azure-storage-file-share/azure/storage/fileshare/aio/_models.py @@ -207,8 +207,7 @@ async def _extract_data_cb(self, get_next_return): for i in segment.directory_items ] self.current_page.extend( - FileProperties._from_generated(i) # pylint: disable = protected-access - for i in segment.file_items + FileProperties._from_generated(i) for i in segment.file_items # pylint: disable = protected-access ) self.current_page.extend( FileProperties._from_generated(i, file_type="SymLink") # pylint: disable = protected-access diff --git a/sdk/storage/azure-storage-file-share/tests/test_nfs.py b/sdk/storage/azure-storage-file-share/tests/test_nfs.py index 5fef053afa30..59c822da8c52 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_nfs.py +++ b/sdk/storage/azure-storage-file-share/tests/test_nfs.py @@ -173,9 +173,7 @@ def send(self, request, **kwargs) -> RequestsTransportResponse: requests_response=MockHttpClientResponse( request.url, self._body, - CaseInsensitiveDict( - {"Content-Type": "application/xml", "Content-Length": str(len(self._body))} - ), + CaseInsensitiveDict({"Content-Type": "application/xml", "Content-Length": str(len(self._body))}), ), ) @@ -192,7 +190,6 @@ def __exit__(self, *args: Any) -> None: pass - class TestStorageFileNFS(StorageRecordedTestCase): fsc: ShareServiceClient = None @@ -492,9 +489,7 @@ def test_list_directories_and_files(self, **kwargs: Any): share_client = self.fsc.get_share_client(self.share_name) directory_name = self._get_directory_name() - directory_client = share_client.create_directory( - directory_name, owner="345", group="123", file_mode="0755" - ) + directory_client = share_client.create_directory(directory_name, owner="345", group="123", file_mode="0755") file_name = self._get_file_name("file1") file_client = directory_client.get_file_client(file_name) @@ -602,4 +597,3 @@ def test_list_directories_and_files_special_types_mock(self): assert socket.nfs_file_type == "Socket" assert socket.file_mode == "0755" assert socket.link_count == 1 - diff --git a/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py b/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py index c5b8cebaa6ba..287a3f13bc84 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py +++ b/sdk/storage/azure-storage-file-share/tests/test_nfs_async.py @@ -49,9 +49,7 @@ async def send(self, request, **kwargs) -> AioHttpTransportResponse: aiohttp_response=MockAioHttpClientResponse( request.url, self._body, - CaseInsensitiveDict( - {"Content-Type": "application/xml", "Content-Length": str(len(self._body))} - ), + CaseInsensitiveDict({"Content-Type": "application/xml", "Content-Length": str(len(self._body))}), ), decompress=False, ) @@ -493,4 +491,3 @@ async def test_list_directories_and_files_special_types_mock(self): assert socket.nfs_file_type == "Socket" assert socket.file_mode == "0755" assert socket.link_count == 1 - From 0a3381f9dfacfe70720866140bebbf3a52752321 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 24 Jun 2026 13:17:56 -0400 Subject: [PATCH 6/7] Test cleanup --- sdk/storage/azure-storage-file-share/tests/test_directory.py | 5 +++-- .../azure-storage-file-share/tests/test_directory_async.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sdk/storage/azure-storage-file-share/tests/test_directory.py b/sdk/storage/azure-storage-file-share/tests/test_directory.py index de11b4a8816d..df2035e66a2d 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_directory.py +++ b/sdk/storage/azure-storage-file-share/tests/test_directory.py @@ -13,7 +13,7 @@ from devtools_testutils.storage import StorageRecordedTestCase from settings.testcase import FileSharePreparer -from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError +from azure.core.exceptions import HttpResponseError, ResourceExistsError, ResourceNotFoundError from azure.storage.fileshare import ( generate_share_sas, NTFSAttributes, @@ -945,9 +945,10 @@ def test_list_subdirectories_and_files_include_all(self, **kwargs): assert props.owner is None assert props.group is None assert props.file_mode is None + try: share_client.delete_share() - except: + except HttpResponseError: pass @FileSharePreparer() diff --git a/sdk/storage/azure-storage-file-share/tests/test_directory_async.py b/sdk/storage/azure-storage-file-share/tests/test_directory_async.py index e0aaf17ad9da..1d99a6fa649f 100644 --- a/sdk/storage/azure-storage-file-share/tests/test_directory_async.py +++ b/sdk/storage/azure-storage-file-share/tests/test_directory_async.py @@ -15,7 +15,7 @@ from devtools_testutils.storage.aio import AsyncStorageRecordedTestCase from settings.testcase import FileSharePreparer -from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError +from azure.core.exceptions import HttpResponseError, ResourceExistsError, ResourceNotFoundError from azure.storage.fileshare import ( generate_share_sas, NTFSAttributes, @@ -1020,7 +1020,7 @@ async def test_list_subdirectories_and_files_include_all_async(self, **kwargs): try: await share_client.delete_share() - except: + except HttpResponseError: pass @FileSharePreparer() From f3f4f1b0c9fd12aa5e8a4e7a04402303ebca2e9a Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 24 Jun 2026 13:24:23 -0400 Subject: [PATCH 7/7] api.md --- sdk/storage/azure-storage-file-share/api.md | 114 +++++++++++++++++- .../azure-storage-file-share/api.metadata.yml | 2 +- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/sdk/storage/azure-storage-file-share/api.md b/sdk/storage/azure-storage-file-share/api.md index dec031a05dee..bf8235e49dcc 100644 --- a/sdk/storage/azure-storage-file-share/api.md +++ b/sdk/storage/azure-storage-file-share/api.md @@ -290,6 +290,7 @@ namespace azure.storage.fileshare last_access_time: Optional[datetime] last_modified: datetime last_write_time: Optional[Union[datetime, str]] + link_count: Optional[int] metadata: Dict[str, str] name: str nfs_file_type: Optional[Literal["Directory"]] @@ -386,6 +387,8 @@ namespace azure.storage.fileshare content_settings: ContentSettings copy: CopyProperties creation_time: Optional[Union[datetime, str]] + device_major: Optional[int] + device_minor: Optional[int] etag: str file_attributes: Union[str, NTFSAttributes] file_id: str @@ -398,9 +401,10 @@ namespace azure.storage.fileshare last_write_time: Optional[Union[datetime, str]] lease: LeaseProperties link_count: Optional[int] + link_text: Optional[str] metadata: Dict[str, str] name: str - nfs_file_type: Optional[Literal["Regular"]] + nfs_file_type: Optional[Literal["Regular", "SymLink", "BlockDevice", "CharacterDevice", "Socket", "Fifo"]] owner: Optional[str] parent_id: Optional[str] path: Optional[str] @@ -455,6 +459,62 @@ namespace azure.storage.fileshare def values(self): ... + class azure.storage.fileshare.FileRange(DictMixin): + cleared: bool + end: int + start: int + + def __contains__(self, key): ... + + def __delitem__(self, key): ... + + def __eq__(self, other): ... + + def __getitem__(self, key): ... + + def __init__( + self, + start: int, + end: int, + *, + cleared: bool = False + ) -> None: ... + + def __len__(self): ... + + def __ne__(self, other): ... + + def __repr__(self): ... + + def __setitem__( + self, + key, + item + ): ... + + def __str__(self): ... + + def get( + self, + key, + default = None + ): ... + + def has_key(self, k): ... + + def items(self): ... + + def keys(self): ... + + def update( + self, + *args, + **kwargs + ): ... + + def values(self): ... + + class azure.storage.fileshare.FileSasPermissions: create: bool = False delete: bool = False @@ -1510,6 +1570,32 @@ namespace azure.storage.fileshare **kwargs: Any ) -> ItemPaged[Handle]: ... + @distributed_trace + def list_ranges( + self, + *, + lease: Union[ShareLeaseClient, str] = ..., + length: Optional[int] = ..., + offset: Optional[int] = ..., + results_per_page: Optional[int] = ..., + timeout: Optional[int] = ..., + **kwargs: Any + ) -> ItemPaged[FileRange]: ... + + @distributed_trace + def list_ranges_diff( + self, + previous_sharesnapshot: Union[str, Dict[str, Any]], + *, + include_renames: Optional[bool] = ..., + lease: Union[ShareLeaseClient, str] = ..., + length: Optional[int] = ..., + offset: Optional[int] = ..., + results_per_page: Optional[int] = ..., + timeout: Optional[int] = ..., + **kwargs: Any + ) -> ItemPaged[FileRange]: ... + @distributed_trace def rename_file( self, @@ -3067,6 +3153,32 @@ namespace azure.storage.fileshare.aio **kwargs: Any ) -> AsyncItemPaged[Handle]: ... + @distributed_trace + def list_ranges( + self, + *, + lease: Union[ShareLeaseClient, str] = ..., + length: Optional[int] = ..., + offset: Optional[int] = ..., + results_per_page: Optional[int] = ..., + timeout: Optional[int] = ..., + **kwargs: Any + ) -> AsyncItemPaged[FileRange]: ... + + @distributed_trace + def list_ranges_diff( + self, + previous_sharesnapshot: Union[str, Dict[str, Any]], + *, + include_renames: Optional[bool] = ..., + lease: Union[ShareLeaseClient, str] = ..., + length: Optional[int] = ..., + offset: Optional[int] = ..., + results_per_page: Optional[int] = ..., + timeout: Optional[int] = ..., + **kwargs: Any + ) -> AsyncItemPaged[FileRange]: ... + @distributed_trace_async async def rename_file( self, diff --git a/sdk/storage/azure-storage-file-share/api.metadata.yml b/sdk/storage/azure-storage-file-share/api.metadata.yml index 971c843d375e..19d55bc24618 100644 --- a/sdk/storage/azure-storage-file-share/api.metadata.yml +++ b/sdk/storage/azure-storage-file-share/api.metadata.yml @@ -1,3 +1,3 @@ -apiMdSha256: 3c2f71c139d0f1e3430e1f6802e9b27f9940c011b1da95748721350c53a0c6f8 +apiMdSha256: 541db07d93195aaab2f93a873a1cecbfa6dd0133b2165cfa5a4f5e874c5aaf71 parserVersion: 0.3.28 pythonVersion: 3.13.14