Skip to content

Commit 46a51ee

Browse files
feat(vortex-python): Add OpenDAL-backed CosStore to the Python object-store API (#8845)
- **Bump `opendal` / `object_store_opendal` to 0.57.0** (in the new `vortex-object-store-opendal` crate); this version enables COS versioning by default. - **Use the upstream `pyo3-object_store` 0.11.0** from crates.io (no longer vendored via `[patch.crates-io]`). The built-in store classes (S3/Azure/GCS/HTTP/Local/Memory) keep their `PyObjectStore` interop, and obstore users can still pass external stores. - **Introduce `vortex-object-store-opendal`**, a new workspace crate that builds an `object_store::ObjectStore` from an OpenDAL `services::Cos` `OpendalStore` (`make_opendal_store(url, properties)`), reused by both the URL registry and the Python store class. - **Add a `CosStore` pyclass (opendal feature)** that wraps an OpenDAL-backed Tencent COS `OpendalStore` and holds it as `Arc<dyn object_store::ObjectStore>`. It is constructed with explicit config (`bucket`, `endpoint`, `secret_id`, `secret_key`, `root`, `disable_config_load`) and can be handed to `vortex.io.read_url(store=...)` / `vortex.io.write(..., store=...)`. - **Introduce `AnyVortexStore` in `io.rs`** so `read_url` / `write` accept both the built-in `pyo3-object_store` classes and the new OpenDAL-backed `CosStore` via a single `store=` argument. - **`cos://` and `oss://` URL resolution** now go through the OpenDAL-backed store, configured via OpenDAL environment variables (`TENCENTCLOUD_SECRET_ID` / `TENCENTCLOUD_SECRET_KEY` / `COS_ENDPOINT`, and `ALIBABACLOUD_ACCESS_KEY_ID` / `ALIBABACLOUD_ACCESS_KEY_SECRET` / `OSS_ENDPOINT`). - **Document the standalone-store usage and add unit tests** (`cos_store_builds_object_store`). **Rationale for this change** Closes: #8844 Vortex's Python API already exposes concrete, standalone store objects for S3/Azure/GCS/HTTP/Local/Memory, but Tencent COS and Alibaba OSS were only reachable through URL-based resolution that relied on environment variables. This adds first-class OpenDAL-backed stores so COS/OSS can be used exactly like the other stores (construct once, pass via `store=`), with better ergonomics and type checking. **What changes are included in this PR?** - new `vortex-object-store-opendal` crate (OpenDAL `services-cos`, `opendal`/`object_store_opendal` 0.57.0) - `pyo3-object_store` pinned to the upstream 0.11.0 (object_store 0.13.x, matching the workspace) — vendored fork removed - new `CosStore` pyclass (gated on the `opendal` feature) wrapping an OpenDAL COS `OpendalStore`, holding `Arc<dyn object_store::ObjectStore>` - `AnyVortexStore` extractor in `io.rs` so `read_url` / `write` accept the new store - `cos://` / `oss://` URL resolution backed by OpenDAL - docs + unit tests for the standalone-store path **What APIs are changed? Are there any user-facing changes?** New public API (Python, `opendal` feature): - `vortex._lib.CosStore(bucket, endpoint, *, secret_id=None, secret_key=None, root=None, disable_config_load=False)` — a standalone `ObjectStore` you can pass to `vortex.io.read_url(url, store=...)` / `vortex.io.write(arrays, path, store=...)`. - `vortex.store.CosStore` / `vortex.store.from_url` now document `cos://` and `oss://` OpenDAL-backed support. --------- Signed-off-by: forwardxu <forwardxu@apache.org>
1 parent b4f8d34 commit 46a51ee

18 files changed

Lines changed: 1007 additions & 40 deletions

File tree

Cargo.lock

Lines changed: 145 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ members = [
1212
"vortex-flatbuffers",
1313
"vortex-metrics",
1414
"vortex-io",
15+
"vortex-object-store-opendal",
1516
"vortex-proto",
1617
"vortex-array",
1718
"vortex-arrow",

docs/api/python/store.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Vortex arrays support reading and writing to many object storage systems:
1313
store/http
1414
store/local
1515
store/memory
16+
store/opendal
1617
store/config
1718

1819
.. autofunction:: vortex.store.from_url

docs/api/python/store/opendal.rst

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
=============
2+
OpenDAL (COS)
3+
=============
4+
5+
Vortex can read from and write to Tencent Cloud COS through
6+
`OpenDAL <https://opendal.apache.org/>`_, which provides native service support.
7+
8+
This store is available only when Vortex is built with the ``opendal`` feature
9+
(e.g. ``maturin develop --features opendal`` or ``cargo build -p vortex-jni --features opendal``).
10+
11+
:class:`vortex.store.CosStore`
12+
==============================
13+
14+
.. py:class:: vortex.store.CosStore(bucket, endpoint, *, secret_id=None, secret_key=None, root=None, disable_config_load=False)
15+
16+
A Tencent Cloud COS object store, backed by OpenDAL. Construct it with explicit
17+
configuration and pass it to
18+
:func:`vortex.io.read_url` / :func:`vortex.io.write` via the ``store=`` argument,
19+
exactly like the built-in S3/Azure/GCS stores.
20+
21+
The class is only available when Vortex is built with the ``opendal`` feature; on
22+
a default build, instantiating it raises :class:`ImportError`.
23+
24+
:param bucket: COS bucket name (e.g. ``"my-bucket"``).
25+
:param endpoint: COS endpoint (e.g. ``"https://cos.ap-guangzhou.myqcloud.com"``).
26+
:param secret_id: Optional Tencent Cloud secret id. Maps to the ``TENCENTCLOUD_SECRET_ID``
27+
environment variable when unset.
28+
:param secret_key: Optional Tencent Cloud secret key. Maps to the
29+
``TENCENTCLOUD_SECRET_KEY`` environment variable when unset.
30+
:param root: Optional key prefix applied to every operation.
31+
:param disable_config_load: When ``True``, disable OpenDAL's automatic config loading
32+
and rely only on the explicit configuration. Defaults to ``False``.
33+
34+
Reading from COS
35+
================
36+
37+
Pass a ``cos://`` URL directly. Credentials and the endpoint are picked up from the environment
38+
variables OpenDAL's COS builder reads (``TENCENTCLOUD_SECRET_ID``, ``TENCENTCLOUD_SECRET_KEY`` and
39+
``COS_ENDPOINT``):
40+
41+
.. code-block:: python
42+
43+
import vortex as vx
44+
45+
a = vx.io.read_url("cos://my-bucket/path/to/dataset.vortex")
46+
47+
Or configure explicitly with :class:`~vortex.store.CosStore` and pass it to
48+
:func:`vortex.io.read_url` via ``store=``:
49+
50+
.. code-block:: python
51+
52+
from vortex.io import read_url
53+
from vortex.store import CosStore
54+
55+
store = CosStore(
56+
bucket="my-bucket",
57+
endpoint="https://cos.ap-guangzhou.myqcloud.com",
58+
secret_id="AKID...",
59+
secret_key="...",
60+
)
61+
62+
# When `store=` is supplied, the path is a key within the store, so the scheme and
63+
# bucket are not part of the path passed to read_url.
64+
a = read_url("path/to/dataset.vortex", store=store)
65+
66+
Passing a store object directly
67+
===============================
68+
69+
``CosStore`` is a concrete store object. You can build one once and pass it
70+
directly to :func:`vortex.io.read_url` / :func:`vortex.io.write` via the ``store=`` argument,
71+
exactly like the built-in S3/Azure/GCS stores:
72+
73+
.. code-block:: python
74+
75+
from vortex.io import read_url
76+
from vortex.store import CosStore
77+
78+
store = CosStore(
79+
bucket="my-bucket",
80+
endpoint="https://cos.ap-guangzhou.myqcloud.com",
81+
secret_id="AKID...",
82+
secret_key="...",
83+
)
84+
85+
# When `store=` is supplied, the path is resolved as a key within the store, so the scheme
86+
# and bucket are not part of the path passed to read_url.
87+
a = read_url("path/to/dataset.vortex", store=store)

docs/conf.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@
5454
git_root = Path(__file__).parent.parent
5555

5656
nitpicky = True # ensures all :class:, :obj:, etc. links are valid
57-
nitpick_ignore = []
57+
nitpick_ignore = [
58+
# `vortex.store.CosStore` is re-exported through the private `vortex.store._cos` module,
59+
# and the `ObjectStore` type alias resolves to the private path. The public class is
60+
# fully documented in `opendal.rst`; the private path is intentionally not.
61+
("py:class", "vortex.store._cos.CosStore"),
62+
]
5863

5964
doctest_global_setup = "import pyarrow; import vortex; import vortex as vx; import random; random.seed(a=0)"
6065
doctest_default_flags = (

vortex-jni/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,17 @@ url = { workspace = true }
3434
vortex = { workspace = true, features = ["object_store", "files"] }
3535
vortex-arrow = { workspace = true }
3636
vortex-geo = { workspace = true }
37+
vortex-object-store-opendal = { path = "../vortex-object-store-opendal", optional = true }
3738
vortex-parquet-variant = { workspace = true }
3839

3940
[dev-dependencies]
4041
jni = { workspace = true, features = ["invocation"] }
4142

43+
[features]
44+
# Enable OpenDAL-backed object stores (Tencent COS) for `cos://` URLs. This pulls in the
45+
# `opendal` dependency, so it is opt-in.
46+
opendal = ["dep:vortex-object-store-opendal"]
47+
4248
[lib]
4349
crate-type = ["cdylib"]
4450

0 commit comments

Comments
 (0)