diff --git a/CHANGELOG.md b/CHANGELOG.md index e917e855..369d2113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ Unreleased * Respect `AzureBlobFileSystem.protocol` tuple when removing protocols from fully-qualified paths provided to `AzureBlobFileSystem` methods. - +* Added `AzureBlobFileSystem.rm_file()` 2025.8.0 -------- diff --git a/adlfs/spec.py b/adlfs/spec.py index 5ff509e1..6514c385 100644 --- a/adlfs/spec.py +++ b/adlfs/spec.py @@ -1287,6 +1287,27 @@ async def _rm_files( sync_wrapper(_rm_files) + async def _rm_file(self, path: str, **kwargs): + """Delete a file. + + Parameters + ---------- + path: str + File to delete. + """ + container_name, p, version_id = self.split_path(path) + try: + async with self.service_client.get_container_client( + container=container_name + ) as cc: + await cc.delete_blob(p, version_id=version_id) + except ResourceNotFoundError as e: + raise FileNotFoundError( + errno.ENOENT, os.strerror(errno.ENOENT), path + ) from e + self.invalidate_cache(path) + self.invalidate_cache(self._parent(path)) + async def _separate_directory_markers_for_non_empty_directories( self, file_paths: typing.Iterable[str] ) -> typing.Tuple[typing.List[str], typing.List[str]]: diff --git a/adlfs/tests/test_spec.py b/adlfs/tests/test_spec.py index bb5d8f78..df49c55f 100644 --- a/adlfs/tests/test_spec.py +++ b/adlfs/tests/test_spec.py @@ -2210,3 +2210,48 @@ def test_write_max_concurrency(storage, max_concurrency, blob_size, blocksize): with fs.open(path, "rb") as f: assert f.read() == data fs.rm(container_name, recursive=True) + + +def test_rm_file(storage): + fs = AzureBlobFileSystem( + account_name=storage.account_name, + connection_string=CONN_STR, + ) + path = "data/test_file.txt" + with fs.open(path, "wb") as f: + f.write(b"test content") + + assert fs.exists(path) + fs.rm_file(path) + with pytest.raises(FileNotFoundError): + fs.ls(path) + assert not fs.exists(path) + assert path not in fs.dircache + + +def test_rm_file_versioned_blob(storage, mocker): + from azure.storage.blob.aio import ContainerClient + + fs = AzureBlobFileSystem( + account_name=storage.account_name, + connection_string=CONN_STR, + version_aware=True, + ) + mock_delete_blob = mocker.patch.object( + ContainerClient, "delete_blob", return_value=None + ) + path = f"data/test_file.txt?versionid={DEFAULT_VERSION_ID}" + fs.rm_file(path) + mock_delete_blob.assert_called_once_with( + "test_file.txt", version_id=DEFAULT_VERSION_ID + ) + + +def test_rm_file_does_not_exist(storage): + fs = AzureBlobFileSystem( + account_name=storage.account_name, + connection_string=CONN_STR, + ) + path = "data/non_existent_file.txt" + with pytest.raises(FileNotFoundError): + fs.rm_file(path)