Expose versioned delete in Python bindings
diff --git a/bindings/python/python/opendal/operator.pyi b/bindings/python/python/opendal/operator.pyi
index 7136720..7483c72 100644
--- a/bindings/python/python/opendal/operator.pyi
+++ b/bindings/python/python/opendal/operator.pyi
@@ -337,7 +337,10 @@
             An awaitable that completes when the directory is created.
         """
     def delete(
-        self, path: builtins.str | os.PathLike | pathlib.Path
+        self,
+        path: builtins.str | os.PathLike | pathlib.Path,
+        *,
+        version: builtins.str | None = None,
     ) -> collections.abc.Awaitable[None]:
         r"""
         Delete a file at the given path.
@@ -350,6 +353,8 @@
         ----------
         path : str
             The path to the file.
+        version : str, optional
+            The version of the file to delete.
 
         Returns
         -------
@@ -2710,7 +2715,12 @@
         path : str
             The path to the directory.
         """
-    def delete(self, path: builtins.str | os.PathLike | pathlib.Path) -> None:
+    def delete(
+        self,
+        path: builtins.str | os.PathLike | pathlib.Path,
+        *,
+        version: builtins.str | None = None,
+    ) -> None:
         r"""
         Delete a file at the given path.
 
@@ -2722,6 +2732,8 @@
         ----------
         path : str
             The path to the file.
+        version : str, optional
+            The version of the file to delete.
         """
     def exists(self, path: builtins.str | os.PathLike | pathlib.Path) -> builtins.bool:
         r"""
diff --git a/bindings/python/src/operator.rs b/bindings/python/src/operator.rs
index abf1bd7..c9f4412 100644
--- a/bindings/python/src/operator.rs
+++ b/bindings/python/src/operator.rs
@@ -523,9 +523,15 @@
     /// ----------
     /// path : str
     ///     The path to the file.
-    pub fn delete(&self, path: PathBuf) -> PyResult<()> {
+    /// version : str, optional
+    ///     The version of the file to delete.
+    #[pyo3(signature = (path, *, version=None))]
+    pub fn delete(&self, path: PathBuf, version: Option<String>) -> PyResult<()> {
         let path = path.to_string_lossy().to_string();
-        self.core.delete(&path).map_err(format_pyerr)
+        let opts = DeleteOptions { version };
+        self.core
+            .delete_options(&path, opts.into())
+            .map_err(format_pyerr)
     }
 
     /// Check if a path exists.
@@ -1293,6 +1299,8 @@
     /// ----------
     /// path : str
     ///     The path to the file.
+    /// version : str, optional
+    ///     The version of the file to delete.
     ///
     /// Returns
     /// -------
@@ -1302,13 +1310,21 @@
         type_repr="collections.abc.Awaitable[None]",
         imports=("collections.abc")
     ))]
-    pub fn delete<'p>(&'p self, py: Python<'p>, path: PathBuf) -> PyResult<Bound<'p, PyAny>> {
+    #[pyo3(signature = (path, *, version=None))]
+    pub fn delete<'p>(
+        &'p self,
+        py: Python<'p>,
+        path: PathBuf,
+        version: Option<String>,
+    ) -> PyResult<Bound<'p, PyAny>> {
         let this = self.core.clone();
         let path = path.to_string_lossy().to_string();
-        future_into_py(
-            py,
-            async move { this.delete(&path).await.map_err(format_pyerr) },
-        )
+        let opts = DeleteOptions { version };
+        future_into_py(py, async move {
+            this.delete_options(&path, opts.into())
+                .await
+                .map_err(format_pyerr)
+        })
     }
 
     /// Check if a path exists.
diff --git a/bindings/python/tests/test_async_delete.py b/bindings/python/tests/test_async_delete.py
index ff3988f..b7596a7 100644
--- a/bindings/python/tests/test_async_delete.py
+++ b/bindings/python/tests/test_async_delete.py
@@ -24,6 +24,16 @@
 
 
 @pytest.mark.asyncio
+@pytest.mark.need_capability("read", "write", "delete")
+async def test_async_delete_with_version_option(service_name, operator, async_operator):
+    path = f"random_file_{str(uuid4())}"
+    await async_operator.write(path, os.urandom(1024))
+    await async_operator.delete(path, version=None)
+    with pytest.raises(NotFound):
+        await async_operator.read(path)
+
+
+@pytest.mark.asyncio
 @pytest.mark.need_capability("read", "write", "delete", "list", "create_dir")
 async def test_async_remove_all(service_name, operator, async_operator):
     parent = f"random_dir_{str(uuid4())}"
diff --git a/bindings/python/tests/test_sync_delete.py b/bindings/python/tests/test_sync_delete.py
index 0260bf8..68fa296 100644
--- a/bindings/python/tests/test_sync_delete.py
+++ b/bindings/python/tests/test_sync_delete.py
@@ -23,6 +23,15 @@
 from opendal.exceptions import NotFound
 
 
+@pytest.mark.need_capability("read", "write", "delete")
+def test_sync_delete_with_version_option(service_name, operator, async_operator):
+    path = f"random_file_{str(uuid4())}"
+    operator.write(path, os.urandom(1024))
+    operator.delete(path, version=None)
+    with pytest.raises(NotFound):
+        operator.read(path)
+
+
 @pytest.mark.need_capability("read", "write", "delete", "list", "create_dir")
 def test_sync_remove_all(service_name, operator, async_operator):
     parent = f"random_dir_{str(uuid4())}"