diff --git a/python-client/.gitignore b/python-client/.gitignore index 3b7cf229361de..b08f40add2721 100644 --- a/python-client/.gitignore +++ b/python-client/.gitignore @@ -1,2 +1,6 @@ windmill-api/ .venv/ +__pycache__/ +*.pyc +*.pyo +*.pyd diff --git a/python-client/tests/wmill_client_test.py b/python-client/tests/wmill_client_test.py index 1acbcb0bf783e..47f6a73dca63c 100644 --- a/python-client/tests/wmill_client_test.py +++ b/python-client/tests/wmill_client_test.py @@ -108,6 +108,56 @@ def test_download_upload_s3_file(self): ) print(file_key) + @unittest.skip("skipping") + def test_remove_s3_file(self): + test_content = b"This file will be deleted" + s3_obj = S3Object(s3="test_file_to_delete.txt") + + # 1. Upload a file + file_key = wmill.write_s3_file(s3_obj, test_content) + print(f"Uploaded file: {file_key}") + + # 2. Verify the file exists by reading it back + content = wmill.load_s3_file(s3_obj) + self.assertEqual(content, test_content) + print(f"File verified: content matches ({len(content)} bytes)") + + # 3. Remove the file + wmill.remove_s3_file(s3_obj) + print("File removed successfully") + + # 4. Verify the file no longer exists (should raise an exception) + with self.assertRaises(Exception): + wmill.load_s3_file(s3_obj) + print("Verified file was removed (read failed as expected)") + + @unittest.skip("skipping") + def test_remove_s3_file_with_resource_path(self): + test_content = b"This file will be deleted with resource path" + s3_obj = S3Object(s3="test_file_with_resource.txt") + + # 1. Upload with explicit resource path + file_key = wmill.write_s3_file( + s3_obj, + test_content, + s3_resource_path=self._resource_path + ) + print(f"Uploaded file: {file_key}") + + # 2. Verify the file exists by reading it back + content = wmill.load_s3_file(s3_obj, s3_resource_path=self._resource_path) + self.assertEqual(content, test_content) + print(f"File verified: content matches ({len(content)} bytes)") + + # 3. Remove with resource path + wmill.remove_s3_file(s3_obj, s3_resource_path=self._resource_path) + print("File removed successfully with resource path") + + # 4. Verify the file no longer exists (should raise an exception) + with self.assertRaises(Exception): + wmill.load_s3_file(s3_obj, s3_resource_path=self._resource_path) + print("Verified file was removed (read failed as expected)") + if __name__ == "__main__": unittest.main() diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index 8c050f554cc70..a089bbde76fb6 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -984,6 +984,43 @@ def write_s3_file( 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 remove_s3_file( + self, + s3object: S3Object | str, + s3_resource_path: str | None = None, + ) -> None: + """ + Remove a file from the workspace S3 bucket + + '''python + from wmill import S3Object + + s3_obj = S3Object(s3="/path/to/my_file.txt") + client.remove_s3_file(s3_obj) + ''' + """ + s3object = parse_s3_object(s3object) + + query_params = {} + if s3object is not None and s3object["s3"] != "": + query_params["file_key"] = s3object["s3"] + if s3_resource_path is not None and s3_resource_path != "": + query_params["s3_resource_path"] = s3_resource_path + if ( + s3object is not None + and "storage" in s3object + and s3object["storage"] is not None + ): + query_params["storage"] = s3object["storage"] + + try: + self.delete( + f"/w/{self.workspace}/job_helpers/delete_s3_file", + params=query_params, + ) + 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. @@ -1679,6 +1716,27 @@ def write_s3_file( ) +@init_global_client +def remove_s3_file( + s3object: S3Object | str, + s3_resource_path: str | None = None, +) -> None: + """ + Remove a file from the workspace S3 bucket + + '''python + from wmill import S3Object + + s3_obj = S3Object(s3="/path/to/my_file.txt") + wmill.remove_s3_file(s3_obj) + ''' + """ + return _client.remove_s3_file( + s3object, + s3_resource_path if s3_resource_path != "" else None, + ) + + @init_global_client def sign_s3_objects(s3_objects: list[S3Object | str]) -> list[S3Object]: """