Skip to content

Commit 82193ba

Browse files
[Storage] Remove batch delete_files() (Azure#24770)
1 parent 3ee4298 commit 82193ba

14 files changed

+20
-2758
lines changed

sdk/storage/azure-storage-file-datalake/CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
- Added support for service version 2021-08-06
77
- Added support for `owner`, `group`, `acl`, `lease_id`, `lease_duration` to both file and directory `create` APIs
88
- Added support for `expiry_options`, `expires_on` to file `create` APIs
9-
- Added `delete_files()` API
109

1110
## 12.7.0 (2022-05-09)
1211

sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_deserialize.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,20 @@ def deserialize_metadata(response, obj, headers): # pylint: disable=unused-argu
115115
return {k[10:]: v for k, v in raw_metadata.items()}
116116

117117

118-
def _decode_error(response, error_message=None, serialized_error=None):
118+
def process_storage_error(storage_error): # pylint:disable=too-many-statements
119119
raise_error = HttpResponseError
120-
error_code = response.headers.get('x-ms-error-code')
120+
serialized = False
121+
if not storage_error.response:
122+
raise storage_error
123+
# If it is one of those three then it has been serialized prior by the generated layer.
124+
if isinstance(storage_error, (ResourceNotFoundError, ClientAuthenticationError, ResourceExistsError)):
125+
serialized = True
126+
error_code = storage_error.response.headers.get('x-ms-error-code')
127+
error_message = storage_error.message
121128
additional_data = {}
122129
error_dict = {}
123130
try:
124-
error_body = ContentDecodePolicy.deserialize_from_http_generics(response)
131+
error_body = ContentDecodePolicy.deserialize_from_http_generics(storage_error.response)
125132
# If it is an XML response
126133
if isinstance(error_body, Element):
127134
error_dict = {
@@ -145,13 +152,9 @@ def _decode_error(response, error_message=None, serialized_error=None):
145152
except DecodeError:
146153
pass
147154

148-
# Convert blob errors to datalake errors
149-
if error_code in [StorageErrorCode.blob_not_found]:
150-
error_code = StorageErrorCode.path_not_found
151-
152155
try:
153156
# This check would be unnecessary if we have already serialized the error.
154-
if error_code and not serialized_error:
157+
if error_code and not serialized:
155158
error_code = StorageErrorCode(error_code)
156159
if error_code in [StorageErrorCode.condition_not_met]:
157160
raise_error = ResourceModifiedError
@@ -195,25 +198,17 @@ def _decode_error(response, error_message=None, serialized_error=None):
195198
for name, info in additional_data.items():
196199
error_message += "\n{}:{}".format(name, info)
197200

198-
if serialized_error:
199-
serialized_error.message = error_message
200-
error = serialized_error
201+
# No need to create an instance if it has already been serialized by the generated layer
202+
if serialized:
203+
storage_error.message = error_message
204+
error = storage_error
201205
else:
202-
error = raise_error(message=error_message, response=response)
206+
error = raise_error(message=error_message, response=storage_error.response)
203207
# Ensure these properties are stored in the error instance as well (not just the error message)
204208
error.error_code = error_code
205209
error.additional_info = additional_data
206210
# error.args is what's surfaced on the traceback - show error message in all cases
207211
error.args = (error.message,)
208-
return error
209-
210-
211-
def process_storage_error(storage_error):
212-
if not storage_error.response:
213-
raise storage_error
214-
# If it is one of those three then it has been serialized prior by the generated layer.
215-
serialized = isinstance(storage_error, (ResourceNotFoundError, ClientAuthenticationError, ResourceExistsError))
216-
error = _decode_error(storage_error.response, storage_error.message, storage_error if serialized else None)
217212

218213
try:
219214
# `from None` prevents us from double printing the exception (suppresses generated layer error context)

sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_file_system_client.py

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# --------------------------------------------------------------------------
66
# pylint: disable=too-many-lines
77
import functools
8-
from typing import Any, Dict, List, Optional, Type, TypeVar, Union, TYPE_CHECKING
8+
from typing import Any, Dict, Optional, Type, TypeVar, Union, TYPE_CHECKING
99

1010
try:
1111
from urllib.parse import urlparse, quote, unquote
@@ -28,7 +28,7 @@
2828
from ._data_lake_lease import DataLakeLeaseClient
2929
from ._generated import AzureDataLakeStorageRESTAPI
3030
from ._generated.models import ListBlobsIncludeItem
31-
from ._deserialize import _decode_error, process_storage_error, is_file_path
31+
from ._deserialize import process_storage_error, is_file_path
3232

3333
if TYPE_CHECKING:
3434
from datetime import datetime
@@ -865,61 +865,6 @@ def _get_root_directory_client(self):
865865
"""
866866
return self.get_directory_client('/')
867867

868-
def delete_files(
869-
self,
870-
*files: str,
871-
**kwargs) -> List[Optional[HttpResponseError]]:
872-
"""Marks the specified files or empty directories for deletion.
873-
874-
The files/empty directories are later deleted during garbage collection.
875-
876-
If a delete retention policy is enabled for the service, then this operation soft deletes the
877-
files/empty directories and retains the files or snapshots for specified number of days.
878-
After specified number of days, files' data is removed from the service during garbage collection.
879-
Soft deleted files/empty directories are accessible through :func:`list_deleted_paths()`.
880-
881-
:param str files:
882-
The files/empty directories to delete. This can be a single file/empty directory, or multiple values can
883-
be supplied, where each value is the name of the file/directory (str).
884-
885-
:keyword ~datetime.datetime if_modified_since:
886-
A DateTime value. Azure expects the date value passed in to be UTC.
887-
If timezone is included, any non-UTC datetimes will be converted to UTC.
888-
If a date is passed in without timezone info, it is assumed to be UTC.
889-
Specify this header to perform the operation only
890-
if the resource has been modified since the specified time.
891-
:keyword ~datetime.datetime if_unmodified_since:
892-
A DateTime value. Azure expects the date value passed in to be UTC.
893-
If timezone is included, any non-UTC datetimes will be converted to UTC.
894-
If a date is passed in without timezone info, it is assumed to be UTC.
895-
Specify this header to perform the operation only if
896-
the resource has not been modified since the specified date/time.
897-
:keyword int timeout:
898-
The timeout parameter is expressed in seconds.
899-
:return: A list containing None for successful operations and
900-
HttpResponseError objects for unsuccessful operations.
901-
:rtype: List[Optional[HttpResponseError]]
902-
903-
.. admonition:: Example:
904-
905-
.. literalinclude:: ../samples/datalake_samples_file_system_async.py
906-
:start-after: [START batch_delete_files_or_empty_directories]
907-
:end-before: [END batch_delete_files_or_empty_directories]
908-
:language: python
909-
:dedent: 4
910-
:caption: Deleting multiple files or empty directories.
911-
"""
912-
results = self._container_client.delete_blobs(raise_on_any_failure=False, *files, **kwargs)
913-
914-
errors = []
915-
for result in results:
916-
if not 200 <= result.status_code < 300:
917-
errors.append(_decode_error(result, result.reason))
918-
else:
919-
errors.append(None)
920-
921-
return errors
922-
923868
def get_directory_client(self, directory # type: Union[DirectoryProperties, str]
924869
):
925870
# type: (...) -> DataLakeDirectoryClient

sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/aio/_file_system_client_async.py

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from azure.core.tracing.decorator_async import distributed_trace_async
2121
from azure.storage.blob.aio import ContainerClient
2222
from .._serialize import get_api_version
23-
from .._deserialize import _decode_error, process_storage_error, is_file_path
23+
from .._deserialize import process_storage_error, is_file_path
2424
from .._generated.models import ListBlobsIncludeItem
2525

2626
from ._data_lake_file_client_async import DataLakeFileClient
@@ -764,62 +764,6 @@ async def delete_file(self, file, # type: Union[FileProperties, str]
764764
await file_client.delete_file(**kwargs)
765765
return file_client
766766

767-
@distributed_trace_async
768-
async def delete_files(
769-
self,
770-
*files: str,
771-
**kwargs) -> List[Optional[HttpResponseError]]:
772-
"""Marks the specified files or empty directories for deletion.
773-
774-
The files/empty directories are later deleted during garbage collection.
775-
776-
If a delete retention policy is enabled for the service, then this operation soft deletes the
777-
files/empty directories and retains the files or snapshots for specified number of days.
778-
After specified number of days, files' data is removed from the service during garbage collection.
779-
Soft deleted files/empty directories are accessible through :func:`list_deleted_paths()`.
780-
781-
:param str files:
782-
The files/empty directories to delete. This can be a single file/empty directory, or multiple values can
783-
be supplied, where each value is the name of the file/directory (str).
784-
785-
:keyword ~datetime.datetime if_modified_since:
786-
A DateTime value. Azure expects the date value passed in to be UTC.
787-
If timezone is included, any non-UTC datetimes will be converted to UTC.
788-
If a date is passed in without timezone info, it is assumed to be UTC.
789-
Specify this header to perform the operation only
790-
if the resource has been modified since the specified time.
791-
:keyword ~datetime.datetime if_unmodified_since:
792-
A DateTime value. Azure expects the date value passed in to be UTC.
793-
If timezone is included, any non-UTC datetimes will be converted to UTC.
794-
If a date is passed in without timezone info, it is assumed to be UTC.
795-
Specify this header to perform the operation only if
796-
the resource has not been modified since the specified date/time.
797-
:keyword int timeout:
798-
The timeout parameter is expressed in seconds.
799-
:return: A list containing None for successful operations and
800-
HttpResponseError objects for unsuccessful operations.
801-
:rtype: List[Optional[HttpResponseError]]
802-
803-
.. admonition:: Example:
804-
805-
.. literalinclude:: ../samples/datalake_samples_file_system_async.py
806-
:start-after: [START batch_delete_files_or_empty_directories]
807-
:end-before: [END batch_delete_files_or_empty_directories]
808-
:language: python
809-
:dedent: 4
810-
:caption: Deleting multiple files or empty directories.
811-
"""
812-
response = await self._container_client.delete_blobs(raise_on_any_failure=False, *files, **kwargs)
813-
814-
errors = []
815-
async for result in response:
816-
if not 200 <= result.status_code < 300:
817-
errors.append(_decode_error(result, result.reason))
818-
else:
819-
errors.append(None)
820-
821-
return errors
822-
823767
@distributed_trace_async
824768
async def _undelete_path(self, deleted_path_name, deletion_id, **kwargs):
825769
# type: (str, str, **Any) -> Union[DataLakeDirectoryClient, DataLakeFileClient]

sdk/storage/azure-storage-file-datalake/samples/datalake_samples_file_system.py

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -209,49 +209,6 @@ def create_file_from_file_system(self):
209209

210210
file_system_client.delete_file_system()
211211

212-
# [START batch_delete_files_or_empty_directories]
213-
def batch_delete_files_or_empty_directories(self):
214-
from azure.storage.filedatalake import FileSystemClient
215-
file_system_client = FileSystemClient.from_connection_string(self.connection_string, "filesystem")
216-
217-
file_system_client.create_file_system()
218-
219-
data = b'hello world'
220-
221-
try:
222-
# create file1
223-
file_system_client.get_file_client('file1').upload_data(data, overwrite=True)
224-
225-
# create file2, then pass file properties in batch delete later
226-
file2 = file_system_client.get_file_client('file2')
227-
file2.upload_data(data, overwrite=True)
228-
229-
# create file3 and batch delete it later only etag matches this file3 etag
230-
file3 = file_system_client.get_file_client('file3')
231-
file3.upload_data(data, overwrite=True)
232-
233-
# create dir1. Empty directory can be deleted using delete_files
234-
file_system_client.get_directory_client('dir1').create_directory()
235-
file_system_client.get_directory_client('dir1').create_file('file4')
236-
237-
except:
238-
pass
239-
240-
response = file_system_client.delete_files(
241-
'file1',
242-
'file2',
243-
'file3',
244-
'dir1', # dir1 is not empty
245-
'dir8', # dir8 doesn't exist
246-
)
247-
print("Total number of sub-responses: " + len(response) + "\n")
248-
print("First failure error code: " + response[3].error_code + "\n")
249-
print("First failure status code: " + response[3].status_code + "\n")
250-
print("Second failure error code: " + response[4].error_code + "\n")
251-
print("Second failure status code: " + response[4].status_code + "\n")
252-
# [END batch_delete_files_or_empty_directories]
253-
254-
255212
if __name__ == '__main__':
256213
sample = FileSystemSamples()
257214
sample.file_system_sample()
@@ -260,4 +217,3 @@ def batch_delete_files_or_empty_directories(self):
260217
sample.list_paths_in_file_system()
261218
sample.get_file_client_from_file_system()
262219
sample.create_file_from_file_system()
263-
sample.batch_delete_files_or_empty_directories()

sdk/storage/azure-storage-file-datalake/samples/datalake_samples_file_system_async.py

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -216,50 +216,6 @@ async def create_file_from_file_system(self):
216216
# [END delete_directory_from_file_system]
217217

218218
await file_system_client.delete_file_system()
219-
220-
# [START batch_delete_files_or_empty_directories]
221-
async def batch_delete_files_or_empty_directories(self):
222-
from azure.storage.filedatalake import FileSystemClient
223-
file_system_client = FileSystemClient.from_connection_string(self.connection_string, "filesystem")
224-
225-
async with file_system_client:
226-
await file_system_client.create_file_system()
227-
228-
data = b'hello world'
229-
230-
try:
231-
# create file1
232-
await file_system_client.get_file_client('file1').upload_data(data, overwrite=True)
233-
234-
# create file2, then pass file properties in batch delete later
235-
file2 = file_system_client.get_file_client('file2')
236-
await file2.upload_data(data, overwrite=True)
237-
238-
# create file3 and batch delete it later only etag matches this file3 etag
239-
file3 = file_system_client.get_file_client('file3')
240-
await file3.upload_data(data, overwrite=True)
241-
242-
# create dir1. Empty directory can be deleted using delete_files
243-
await file_system_client.get_directory_client('dir1').create_directory()
244-
await file_system_client.get_directory_client('dir1').create_file('file4')
245-
246-
except:
247-
pass
248-
249-
response = await file_system_client.delete_files(
250-
'file1',
251-
'file2',
252-
'file3',
253-
'dir1', # dir1 is not empty
254-
'dir8', # dir8 doesn't exist
255-
)
256-
print("Total number of sub-responses: " + len(response) + "\n")
257-
print("First failure error code: " + response[3].error_code + "\n")
258-
print("First failure status code: " + response[3].status_code + "\n")
259-
print("Second failure error code: " + response[4].error_code + "\n")
260-
print("Second failure status code: " + response[4].status_code + "\n")
261-
# [END batch_delete_files_or_empty_directories]
262-
263219

264220
async def run():
265221
sample = FileSystemSamplesAsync()
@@ -269,7 +225,6 @@ async def run():
269225
await sample.list_paths_in_file_system()
270226
await sample.get_file_client_from_file_system()
271227
await sample.create_file_from_file_system()
272-
await sample.batch_delete_files_or_empty_directories()
273228

274229
if __name__ == '__main__':
275230
loop = asyncio.get_event_loop()

0 commit comments

Comments
 (0)