diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index df1da905df16..4bf502e8e187 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -36,4 +36,4 @@ futures = "0.3.28" opendal.workspace = true pyo3 = "0.19" pyo3-asyncio = { version = "0.19", features = ["tokio-runtime"] } -tokio = "1" +tokio = "1" \ No newline at end of file diff --git a/bindings/python/python/opendal/__init__.pyi b/bindings/python/python/opendal/__init__.pyi index 2ee1960b98aa..55164c816447 100644 --- a/bindings/python/python/opendal/__init__.pyi +++ b/bindings/python/python/opendal/__init__.pyi @@ -38,6 +38,7 @@ class Operator: def delete(self, path: str): ... def list(self, path: str) -> Iterable[Entry]: ... def scan(self, path: str) -> Iterable[Entry]: ... + def capability(self) -> Capability: ... class AsyncOperator: def __init__(self, scheme: str, **kwargs): ... @@ -63,6 +64,7 @@ class AsyncOperator: async def presign_write( self, path: str, expire_second: int ) -> PresignedRequest: ... + def capability(self) -> Capability: ... class Reader: def read(self, size: Optional[int] = None) -> memoryview: ... @@ -107,3 +109,52 @@ class PresignedRequest: def method(self) -> str: ... @property def headers(self) -> dict[str, str]: ... + +class Capability: + stat: bool + stat_with_if_match: bool + stat_with_if_none_match: bool + + read: bool + read_can_seek: bool + read_can_next: bool + read_with_range: bool + read_with_if_match: bool + read_with_if_none_match: bool + read_with_override_cache_control: bool + read_with_override_content_disposition: bool + read_with_override_content_type: bool + + write: bool + write_can_multi: bool + write_can_empty: bool + write_can_append: bool + write_with_content_type: bool + write_with_content_disposition: bool + write_with_cache_control: bool + write_multi_max_size: Optional[int] + write_multi_min_size: Optional[int] + write_multi_align_size: Optional[int] + write_total_max_size: Optional[int] + + create_dir: bool + delete: bool + copy: bool + rename: bool + + list: bool + list_with_limit: bool + list_with_start_after: bool + list_with_delimiter_slash: bool + list_without_delimiter: bool + + presign: bool + presign_read: bool + presign_stat: bool + presign_write: bool + + batch: bool + batch_delete: bool + batch_max_operations: Optional[int] + + blocking: bool diff --git a/bindings/python/src/asyncio.rs b/bindings/python/src/asyncio.rs index 78693d577401..1cedac75ee47 100644 --- a/bindings/python/src/asyncio.rs +++ b/bindings/python/src/asyncio.rs @@ -46,6 +46,8 @@ use crate::Entry; use crate::Metadata; use crate::PresignedRequest; +use crate::capability; + /// `AsyncOperator` is the entry for all public async APIs /// /// Create a new `AsyncOperator` with the given `scheme` and options(`**kwargs`). @@ -250,6 +252,10 @@ impl AsyncOperator { }) } + pub fn capability(&self) -> PyResult { + Ok(capability::Capability::new(self.0.info().full_capability())) + } + fn __repr__(&self) -> String { let info = self.0.info(); let name = info.name(); diff --git a/bindings/python/src/capability.rs b/bindings/python/src/capability.rs new file mode 100644 index 000000000000..f027199aaa4d --- /dev/null +++ b/bindings/python/src/capability.rs @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use pyo3::prelude::*; + +#[pyclass(get_all, module = "opendal")] +pub struct Capability { + pub stat: bool, + pub stat_with_if_match: bool, + pub stat_with_if_none_match: bool, + pub read: bool, + pub read_can_seek: bool, + pub read_can_next: bool, + pub read_with_range: bool, + pub read_with_if_match: bool, + pub read_with_if_none_match: bool, + pub read_with_override_cache_control: bool, + pub read_with_override_content_disposition: bool, + pub read_with_override_content_type: bool, + pub write: bool, + pub write_can_multi: bool, + pub write_can_empty: bool, + pub write_can_append: bool, + pub write_with_content_type: bool, + pub write_with_content_disposition: bool, + pub write_with_cache_control: bool, + pub write_multi_max_size: Option, + pub write_multi_min_size: Option, + pub write_multi_align_size: Option, + pub write_total_max_size: Option, + pub create_dir: bool, + pub delete: bool, + pub copy: bool, + pub rename: bool, + pub list: bool, + pub list_with_limit: bool, + pub list_with_start_after: bool, + pub list_with_delimiter_slash: bool, + pub list_without_delimiter: bool, + pub presign: bool, + pub presign_read: bool, + pub presign_stat: bool, + pub presign_write: bool, + pub batch: bool, + pub batch_delete: bool, + pub batch_max_operations: Option, + pub blocking: bool, +} + +impl Capability { + pub fn new(capability: opendal::Capability) -> Self { + Self { + stat: capability.stat, + stat_with_if_match: capability.stat_with_if_match, + stat_with_if_none_match: capability.stat_with_if_none_match, + read: capability.read, + read_can_seek: capability.read_can_seek, + read_can_next: capability.read_can_next, + read_with_range: capability.read_with_range, + read_with_if_match: capability.read_with_if_match, + read_with_if_none_match: capability.read_with_if_none_match, + read_with_override_cache_control: capability.read_with_override_cache_control, + read_with_override_content_disposition: capability + .read_with_override_content_disposition, + read_with_override_content_type: capability.read_with_override_content_type, + write: capability.write, + write_can_multi: capability.write_can_multi, + write_can_empty: capability.write_can_empty, + write_can_append: capability.write_can_append, + write_with_content_type: capability.write_with_content_type, + write_with_content_disposition: capability.write_with_content_disposition, + write_with_cache_control: capability.write_with_cache_control, + write_multi_max_size: capability.write_multi_max_size, + write_multi_min_size: capability.write_multi_min_size, + write_multi_align_size: capability.write_multi_align_size, + write_total_max_size: capability.write_total_max_size, + create_dir: capability.create_dir, + delete: capability.delete, + copy: capability.copy, + rename: capability.rename, + list: capability.list, + list_with_limit: capability.list_with_limit, + list_with_start_after: capability.list_with_start_after, + list_with_delimiter_slash: capability.list_with_delimiter_slash, + list_without_delimiter: capability.list_without_delimiter, + presign: capability.presign, + presign_read: capability.presign_read, + presign_stat: capability.presign_stat, + presign_write: capability.presign_write, + batch: capability.batch, + batch_delete: capability.batch_delete, + batch_max_operations: capability.batch_max_operations, + blocking: capability.blocking, + } + } +} diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 83eb4a52c5b7..1ee0884f9098 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -40,6 +40,7 @@ use pyo3::types::PyDict; use pyo3::AsPyPointer; mod asyncio; +mod capability; mod layers; use crate::asyncio::*; @@ -228,6 +229,10 @@ impl Operator { )) } + pub fn capability(&self) -> PyResult { + Ok(capability::Capability::new(self.0.info().full_capability())) + } + fn __repr__(&self) -> String { let info = self.0.info(); let name = info.name(); @@ -572,6 +577,7 @@ fn _opendal(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add("Error", py.get_type::())?; let layers = layers::create_submodule(py)?; diff --git a/bindings/python/tests/test_services.py b/bindings/python/tests/test_services.py index 836988e7d109..1d23cefc2e93 100644 --- a/bindings/python/tests/test_services.py +++ b/bindings/python/tests/test_services.py @@ -199,6 +199,17 @@ async def test_async_delete(self): await self.async_operator.delete(filename) with pytest.raises(FileNotFoundError): await self.operator.stat(filename) + + def test_capability(self): + cap = self.operator.capability() + assert cap is not None + assert cap.read is not None + + def test_capability_exception(self): + cap = self.operator.capability() + assert cap is not None + with pytest.raises(AttributeError) as e_info: + cap.read_demo class TestS3(AbstractTestSuite): diff --git a/core/src/types/capability.rs b/core/src/types/capability.rs index 437a3e9ba958..8c1a4977974d 100644 --- a/core/src/types/capability.rs +++ b/core/src/types/capability.rs @@ -16,7 +16,6 @@ // under the License. use std::fmt::Debug; - /// Capability is used to describe what operations are supported /// by current Operator. ///