diff --git a/python-client/tests/wmill_client_test.py b/python-client/tests/wmill_client_test.py index 1acbcb0bf7..3309e1ab5f 100644 --- a/python-client/tests/wmill_client_test.py +++ b/python-client/tests/wmill_client_test.py @@ -108,6 +108,21 @@ SET s3_secret_access_key='80yMndIMcyXwEujxVNINQbf0tBlIzRaLPyM2m1n4'; ) print(file_key) + @unittest.skip("skipping") + def test_delete_s3_object(self): + # Upload a temporary file + s3_obj = wmill.write_s3_file( + S3Object(s3="_wmill_test_delete_s3_object.txt"), b"delete_s3_object test content" + ) + # Verify it exists + content = wmill.load_s3_file(s3_obj) + self.assertEqual(content, b"delete_s3_object test content") + # Delete it + wmill.delete_s3_object(s3_obj) + # Verify it's gone + with self.assertRaises(Exception): + wmill.load_s3_file(s3_obj) + if __name__ == "__main__": unittest.main() diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index b36c7a0401..3b9a9f3f45 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -984,6 +984,40 @@ class Windmill: raise Exception("Could not write file to S3") from e return S3Object(s3=response["file_key"], storage=s3object.get("storage") if s3object else None) + def delete_s3_object( + self, + s3object: S3Object | str, + s3_resource_path: str | None = None, + ) -> None: + """ + Permanently delete a file from the workspace S3 bucket. + + '''python + from wmill import S3Object + + s3_obj = S3Object(s3="/path/to/my_file.txt") + client.delete_s3_object(s3_obj) + ''' + """ + s3object = parse_s3_object(s3object) + query_params: Dict[str, Any] = {"file_key": s3object["s3"]} + if s3_resource_path is not None and s3_resource_path != "": + query_params["s3_resource_path"] = s3_resource_path + if "storage" in s3object and s3object["storage"] is not None: + query_params["storage"] = s3object["storage"] + try: + resp = self.client.delete( + f"/w/{self.workspace}/job_helpers/delete_s3_file", + params=query_params, + ) + resp.raise_for_status() + except httpx.HTTPStatusError as err: + error = f"{err.request.url}: {err.response.status_code}, {err.response.text}" + logger.error(error) + raise Exception(error) + except Exception as e: + raise Exception("Could not delete file from S3") from e + def sign_s3_objects(self, s3_objects: list[S3Object | str]) -> list[S3Object]: """Sign S3 objects for use by anonymous users in public apps. @@ -1692,6 +1726,20 @@ def write_s3_file( ) +@init_global_client +def delete_s3_object( + s3object: S3Object | str, + s3_resource_path: str | None = None, +) -> None: + """ + Permanently delete a file from the workspace S3 bucket. + """ + return _client.delete_s3_object( + s3object, + s3_resource_path if s3_resource_path != "" else None, + ) + + @init_global_client def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object]: """ @@ -2368,4 +2416,4 @@ def parse_sql_client_name(name: str) -> tuple[str, Optional[str]]: name, schema = name.split(":", 1) if not name: name = "main" - return name, schema \ No newline at end of file + return name, schema