Skip to content

Commit 13bb893

Browse files
authored
Prefix store (#117)
* Prefix store * Add tests for prefix * Bump python beta version
1 parent 7074df6 commit 13bb893

File tree

11 files changed

+121
-6
lines changed

11 files changed

+121
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/api/store/middleware.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Middleware
2+
3+
Wrappers around other `ObjectStore` instances to provide monitoring or other modifications.
4+
5+
::: obstore.store.PrefixStore

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ nav:
3131
- api/store/local.md
3232
- api/store/memory.md
3333
- api/store/config.md
34+
- api/store/middleware.md
3435
- api/copy.md
3536
- api/delete.md
3637
- api/get.md

obstore/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "obstore"
3-
version = "0.3.0-beta.9"
3+
version = "0.3.0-beta.10"
44
authors = { workspace = true }
55
edition = { workspace = true }
66
description = "A Python interface to the Rust object_store crate, providing a uniform API for interacting with object storage services and local files."

obstore/python/obstore/store/__init__.pyi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ from ._client import ClientConfigKey as ClientConfigKey
99
from ._gcs import GCSConfigKey as GCSConfigKey
1010
from ._gcs import GCSStore as GCSStore
1111
from ._http import HTTPStore as HTTPStore
12+
from ._prefix import PrefixStore as PrefixStore
1213
from ._retry import BackoffConfig as BackoffConfig
1314
from ._retry import RetryConfig as RetryConfig
1415

@@ -57,5 +58,7 @@ class MemoryStore:
5758
def __init__(self) -> None: ...
5859
def __repr__(self) -> str: ...
5960

60-
ObjectStore = AzureStore | GCSStore | HTTPStore | S3Store | LocalStore | MemoryStore
61+
ObjectStore = (
62+
AzureStore | GCSStore | HTTPStore | S3Store | LocalStore | MemoryStore | PrefixStore
63+
)
6164
"""All supported ObjectStore implementations."""
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from obstore.store import ObjectStore
2+
3+
class PrefixStore:
4+
"""Store wrapper that applies a constant prefix to all paths handled by the store.
5+
6+
**Example**:
7+
8+
```py
9+
import obstore as obs
10+
from obstore.store import MemoryStore, PrefixStore
11+
12+
store = MemoryStore()
13+
14+
data = b"the quick brown fox jumps over the lazy dog"
15+
path = "a/b/c/data.txt"
16+
17+
obs.put(store, path, data)
18+
19+
prefix_store = PrefixStore(store, "a/")
20+
assert obs.get(prefix_store, "b/c/data.txt").bytes() == data
21+
22+
# The / after the passed-in prefix is inferred
23+
prefix_store2 = PrefixStore(store, "a")
24+
assert obs.get(prefix_store2, "b/c/data.txt").bytes() == data
25+
26+
# The prefix is removed from list results
27+
assert obs.list(prefix_store).collect()[0]["path"] == "b/c/data.txt"
28+
29+
# More deeply nested prefix
30+
prefix_store3 = PrefixStore(store, "a/b/c")
31+
assert obs.get(prefix_store3, "data.txt").bytes() == data
32+
```
33+
"""
34+
def __init__(self, store: ObjectStore, prefix: str) -> None:
35+
"""Create a new PrefixStore with the provided prefix.
36+
37+
Args:
38+
store: The underlying store to wrap.
39+
prefix: If the prefix does not end with `/`, one will be added.
40+
"""
41+
42+
def __repr__(self) -> str: ...

pyo3-object_store/src/api.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use pyo3::intern;
22
use pyo3::prelude::*;
33

44
use crate::error::*;
5-
use crate::{PyAzureStore, PyGCSStore, PyHttpStore, PyLocalStore, PyMemoryStore, PyS3Store};
5+
use crate::{
6+
PyAzureStore, PyGCSStore, PyHttpStore, PyLocalStore, PyMemoryStore, PyPrefixStore, PyS3Store,
7+
};
68

79
/// Export the default Python API as a submodule named `store` within the given parent module
810
///
@@ -50,6 +52,7 @@ pub fn register_store_module(
5052
child_module.add_class::<PyLocalStore>()?;
5153
child_module.add_class::<PyMemoryStore>()?;
5254
child_module.add_class::<PyS3Store>()?;
55+
child_module.add_class::<PyPrefixStore>()?;
5356

5457
parent_module.add_submodule(&child_module)?;
5558

pyo3-object_store/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod gcp;
1010
mod http;
1111
mod local;
1212
mod memory;
13+
mod prefix;
1314
mod retry;
1415
mod store;
1516

@@ -22,4 +23,5 @@ pub use gcp::PyGCSStore;
2223
pub use http::PyHttpStore;
2324
pub use local::PyLocalStore;
2425
pub use memory::PyMemoryStore;
26+
pub use prefix::PyPrefixStore;
2527
pub use store::PyObjectStore;

pyo3-object_store/src/prefix.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use std::sync::Arc;
2+
3+
use pyo3::prelude::*;
4+
5+
use object_store::prefix::PrefixStore;
6+
use object_store::ObjectStore;
7+
8+
use crate::PyObjectStore;
9+
10+
/// A Python-facing wrapper around a [`PrefixStore`].
11+
#[pyclass(name = "PrefixStore", frozen)]
12+
pub struct PyPrefixStore(Arc<PrefixStore<Arc<dyn ObjectStore>>>);
13+
14+
impl AsRef<Arc<PrefixStore<Arc<dyn ObjectStore>>>> for PyPrefixStore {
15+
fn as_ref(&self) -> &Arc<PrefixStore<Arc<dyn ObjectStore>>> {
16+
&self.0
17+
}
18+
}
19+
20+
#[pymethods]
21+
impl PyPrefixStore {
22+
#[new]
23+
fn new(store: PyObjectStore, prefix: String) -> Self {
24+
Self(Arc::new(PrefixStore::new(store.into_inner(), prefix)))
25+
}
26+
27+
fn __repr__(&self) -> String {
28+
self.0.to_string()
29+
}
30+
}

pyo3-object_store/src/store.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use pyo3::intern;
66
use pyo3::prelude::*;
77
use pyo3::pybacked::PyBackedStr;
88

9-
use crate::http::PyHttpStore;
10-
use crate::{PyAzureStore, PyGCSStore, PyLocalStore, PyMemoryStore, PyS3Store};
9+
use crate::{
10+
PyAzureStore, PyGCSStore, PyHttpStore, PyLocalStore, PyMemoryStore, PyPrefixStore, PyS3Store,
11+
};
1112

1213
/// A wrapper around a Rust ObjectStore instance that allows any rust-native implementation of
1314
/// ObjectStore.
@@ -29,6 +30,8 @@ impl<'py> FromPyObject<'py> for PyObjectStore {
2930
Ok(Self(store.get().as_ref().clone()))
3031
} else if let Ok(store) = ob.downcast::<PyMemoryStore>() {
3132
Ok(Self(store.get().as_ref().clone()))
33+
} else if let Ok(store) = ob.downcast::<PyPrefixStore>() {
34+
Ok(Self(store.get().as_ref().clone()))
3235
} else {
3336
let py = ob.py();
3437
// Check for object-store instance from other library
@@ -43,6 +46,7 @@ impl<'py> FromPyObject<'py> for PyObjectStore {
4346
"LocalStore",
4447
"MemoryStore",
4548
"S3Store",
49+
"PrefixStore",
4650
]
4751
.contains(&cls_name.as_ref())
4852
{

0 commit comments

Comments
 (0)