Skip to content

Commit 17ac40d

Browse files
authored
Add delete_object support in s3torchconnectorclient (#129)
- Add delete_object support in s3torchconnectorclient --------- Co-authored-by: Simon Beal <[email protected]>
1 parent a4a757c commit 17ac40d

File tree

6 files changed

+80
-2
lines changed

6 files changed

+80
-2
lines changed

s3torchconnectorclient/python/src/s3torchconnectorclient/_mountpoint_s3_client.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class MountpointS3Client:
3030
self, bucket: str, prefix: str = "", delimiter: str = "", max_keys: int = 1000
3131
) -> ListObjectStream: ...
3232
def head_object(self, bucket: str, key: str) -> ObjectInfo: ...
33+
def delete_object(self, bucket: str, key: str) -> None: ...
3334

3435
class MockMountpointS3Client:
3536
def __init__(

s3torchconnectorclient/python/tst/integration/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,8 @@ def put_object_tests_directory(request) -> BucketPrefixFixture:
107107
@pytest.fixture
108108
def checkpoint_directory(request) -> BucketPrefixFixture:
109109
return get_test_bucket_prefix(f"{request.node.name}/checkpoint_directory")
110+
111+
112+
@pytest.fixture
113+
def empty_directory(request) -> BucketPrefixFixture:
114+
return get_test_bucket_prefix(f"{request.node.name}/empty_directory")

s3torchconnectorclient/python/tst/integration/test_mountpoint_s3_integration.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,37 @@ def test_head_object(sample_directory):
258258
assert object_info.etag == expected_etag
259259

260260

261+
def test_delete_object(sample_directory):
262+
client = MountpointS3Client(sample_directory.region, TEST_USER_AGENT_PREFIX)
263+
client.delete_object(
264+
sample_directory.bucket,
265+
f"{sample_directory.prefix}hello_world.txt",
266+
)
267+
268+
with pytest.raises(S3Exception, match="Service error: The key does not exist"):
269+
next(
270+
client.get_object(
271+
sample_directory.bucket, f"{sample_directory.prefix}hello_world.txt"
272+
)
273+
)
274+
275+
276+
def test_delete_object_does_not_exist(empty_directory):
277+
client = MountpointS3Client(empty_directory.region, TEST_USER_AGENT_PREFIX)
278+
client.delete_object(
279+
empty_directory.bucket, f"{empty_directory.prefix}hello_world.txt"
280+
)
281+
# Assert no exception is thrown in case the object already doesn't exist - implicit
282+
283+
284+
def test_delete_object_invalid_bucket(empty_directory):
285+
client = MountpointS3Client(empty_directory.region, TEST_USER_AGENT_PREFIX)
286+
with pytest.raises(S3Exception, match="Service error: The bucket does not exist"):
287+
client.delete_object(
288+
f"{empty_directory.bucket}-{uuid.uuid4()}", empty_directory.prefix
289+
)
290+
291+
261292
def _parse_list_result(stream: ListObjectStream, max_keys: int):
262293
object_infos = []
263294
i = 0

s3torchconnectorclient/python/tst/unit/test_mountpoint_s3_client.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,33 @@ def test_mountpoint_client_pickles():
288288
assert client.no_sign_request == loaded.no_sign_request == expected_no_sign_request
289289

290290

291+
def test_delete_object():
292+
key = "hello_world.txt"
293+
mock_client = MockMountpointS3Client(REGION, MOCK_BUCKET)
294+
mock_client.add_object(key, b"data")
295+
client = mock_client.create_mocked_client()
296+
297+
client.delete_object(MOCK_BUCKET, key)
298+
299+
with pytest.raises(S3Exception, match="Service error: The key does not exist"):
300+
client.get_object(MOCK_BUCKET, key)
301+
302+
303+
def test_delete_object_already_deleted():
304+
mock_client = MockMountpointS3Client(REGION, MOCK_BUCKET)
305+
client = mock_client.create_mocked_client()
306+
307+
client.delete_object(MOCK_BUCKET, "hello_world.txt")
308+
309+
310+
def test_delete_object_non_existent_bucket():
311+
mock_client = MockMountpointS3Client(REGION, MOCK_BUCKET)
312+
client = mock_client.create_mocked_client()
313+
314+
with pytest.raises(S3Exception, match="Service error: The bucket does not exist"):
315+
client.delete_object("bucket2", "hello_world.txt")
316+
317+
291318
def _assert_isinstance(obj, expected: type):
292319
assert isinstance(obj, expected), f"Expected a {expected}, got {type(obj)=}"
293320

s3torchconnectorclient/rust/src/mountpoint_s3_client.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use std::sync::Arc;
77

88
use mountpoint_s3_client::config::{EndpointConfig, S3ClientAuthConfig, S3ClientConfig};
99
use mountpoint_s3_client::types::PutObjectParams;
10-
use mountpoint_s3_client::{ObjectClient, S3CrtClient};
1110
use mountpoint_s3_client::user_agent::UserAgent;
11+
use mountpoint_s3_client::{ObjectClient, S3CrtClient};
1212
use nix::unistd::Pid;
1313
use pyo3::types::PyTuple;
1414
use pyo3::{pyclass, pymethods, PyRef, PyResult, ToPyObject};
@@ -64,7 +64,8 @@ impl MountpointS3Client {
6464
let endpoint_config = EndpointConfig::new(&region);
6565
let auth_config = auth_config(profile.as_deref(), no_sign_request);
6666

67-
let user_agent_suffix = &format!("{}/{}", build_info::PACKAGE_NAME, build_info::FULL_VERSION);
67+
let user_agent_suffix =
68+
&format!("{}/{}", build_info::PACKAGE_NAME, build_info::FULL_VERSION);
6869
let mut user_agent_string = &format!("{} {}", &user_agent_prefix, &user_agent_suffix);
6970
if user_agent_prefix.ends_with(user_agent_suffix) {
7071
// If we unpickle a client, we should not append the suffix again
@@ -130,6 +131,10 @@ impl MountpointS3Client {
130131
slf.client.head_object(slf.py(), bucket, key)
131132
}
132133

134+
pub fn delete_object(slf: PyRef<'_, Self>, bucket: String, key: String) -> PyResult<()> {
135+
slf.client.delete_object(slf.py(), bucket, key)
136+
}
137+
133138
pub fn __getnewargs__(slf: PyRef<'_, Self>) -> PyResult<&PyTuple> {
134139
let py = slf.py();
135140
let state = [

s3torchconnectorclient/rust/src/mountpoint_s3_client_inner.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub(crate) trait MountpointS3ClientInner {
4141
params: PutObjectParams,
4242
) -> PyResult<PutObjectStream>;
4343
fn head_object(&self, py: Python, bucket: String, key: String) -> PyResult<PyObjectInfo>;
44+
fn delete_object(&self, py: Python, bucket: String, key: String) -> PyResult<()>;
4445
}
4546

4647
pub(crate) struct MountpointS3ClientInnerImpl<T: ObjectClient> {
@@ -108,4 +109,12 @@ where
108109
let request = py.allow_threads(|| block_on(request).map_err(python_exception))?;
109110
Ok(PyObjectInfo::from_object_info(request.object))
110111
}
112+
113+
fn delete_object(&self, py: Python, bucket: String, key: String) -> PyResult<()> {
114+
let request = self.client.delete_object(&bucket, &key);
115+
116+
// TODO - Look at use of `block_on` and see if we can future this.
117+
py.allow_threads(|| block_on(request).map_err(python_exception))?;
118+
Ok(())
119+
}
111120
}

0 commit comments

Comments
 (0)