Skip to content

Commit e71b2cc

Browse files
authored
Expose ObjectMeta from readable file API (#176)
1 parent 3e1fbdc commit e71b2cc

File tree

3 files changed

+49
-11
lines changed

3 files changed

+49
-11
lines changed

obstore/python/obstore/_buffered.pyi

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from typing import Dict, List, Self
55

66
from ._attributes import Attributes
77
from ._bytes import Bytes
8+
from ._list import ObjectMeta
89
from .store import ObjectStore
910

1011
if sys.version_info >= (3, 12):
@@ -65,6 +66,10 @@ class ReadableFile:
6566
This is currently a no-op.
6667
"""
6768

69+
@property
70+
def meta(self) -> ObjectMeta:
71+
"""Access the metadata of the underlying file"""
72+
6873
def read(self, size: int | None = None, /) -> Bytes:
6974
"""
7075
Read up to `size` bytes from the object and return them. As a convenience, if
@@ -97,6 +102,10 @@ class ReadableFile:
97102
def seekable(self) -> bool:
98103
"""Return True if the stream supports random access."""
99104

105+
@property
106+
def size(self) -> int:
107+
"""The size in bytes of the object."""
108+
100109
def tell(self) -> int:
101110
"""Return the current stream position."""
102111

@@ -129,6 +138,10 @@ class AsyncReadableFile:
129138
This is currently a no-op.
130139
"""
131140

141+
@property
142+
def meta(self) -> ObjectMeta:
143+
"""Access the metadata of the underlying file"""
144+
132145
async def read(self, size: int | None = None, /) -> Bytes:
133146
"""
134147
Read up to `size` bytes from the object and return them. As a convenience, if
@@ -161,6 +174,10 @@ class AsyncReadableFile:
161174
def seekable(self) -> bool:
162175
"""Return True if the stream supports random access."""
163176

177+
@property
178+
def size(self) -> int:
179+
"""The size in bytes of the object."""
180+
164181
async def tell(self) -> int:
165182
"""Return the current stream position."""
166183

obstore/src/buffered.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::sync::Arc;
33

44
use bytes::Bytes;
55
use object_store::buffered::{BufReader, BufWriter};
6-
use object_store::ObjectStore;
6+
use object_store::{ObjectMeta, ObjectStore};
77
use pyo3::exceptions::{PyIOError, PyStopAsyncIteration, PyStopIteration};
88
use pyo3::prelude::*;
99
use pyo3::types::PyString;
@@ -15,6 +15,7 @@ use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, Line
1515
use tokio::sync::Mutex;
1616

1717
use crate::attributes::PyAttributes;
18+
use crate::list::PyObjectMeta;
1819
use crate::runtime::get_runtime;
1920
use crate::tags::PyTagSet;
2021

@@ -28,8 +29,9 @@ pub(crate) fn open_reader(
2829
) -> PyObjectStoreResult<PyReadableFile> {
2930
let store = store.into_inner();
3031
let runtime = get_runtime(py)?;
31-
let reader = py.allow_threads(|| runtime.block_on(create_reader(store, path, buffer_size)))?;
32-
Ok(PyReadableFile::new(reader, false))
32+
let (reader, meta) =
33+
py.allow_threads(|| runtime.block_on(create_reader(store, path, buffer_size)))?;
34+
Ok(PyReadableFile::new(reader, meta, false))
3335
}
3436

3537
#[pyfunction]
@@ -42,34 +44,37 @@ pub(crate) fn open_reader_async(
4244
) -> PyResult<Bound<PyAny>> {
4345
let store = store.into_inner();
4446
future_into_py(py, async move {
45-
let reader = create_reader(store, path, buffer_size).await?;
46-
Ok(PyReadableFile::new(reader, true))
47+
let (reader, meta) = create_reader(store, path, buffer_size).await?;
48+
Ok(PyReadableFile::new(reader, meta, true))
4749
})
4850
}
4951

5052
async fn create_reader(
5153
store: Arc<dyn ObjectStore>,
5254
path: String,
5355
capacity: usize,
54-
) -> PyObjectStoreResult<Arc<Mutex<BufReader>>> {
56+
) -> PyObjectStoreResult<(BufReader, ObjectMeta)> {
5557
let meta = store
5658
.head(&path.into())
5759
.await
5860
.map_err(PyObjectStoreError::ObjectStoreError)?;
59-
Ok(Arc::new(Mutex::new(BufReader::with_capacity(
60-
store, &meta, capacity,
61-
))))
61+
Ok((BufReader::with_capacity(store, &meta, capacity), meta))
6262
}
6363

6464
#[pyclass(name = "ReadableFile", frozen)]
6565
pub(crate) struct PyReadableFile {
6666
reader: Arc<Mutex<BufReader>>,
67+
meta: ObjectMeta,
6768
r#async: bool,
6869
}
6970

7071
impl PyReadableFile {
71-
fn new(reader: Arc<Mutex<BufReader>>, r#async: bool) -> Self {
72-
Self { reader, r#async }
72+
fn new(reader: BufReader, meta: ObjectMeta, r#async: bool) -> Self {
73+
Self {
74+
reader: Arc::new(Mutex::new(reader)),
75+
meta,
76+
r#async,
77+
}
7378
}
7479
}
7580

@@ -88,6 +93,11 @@ impl PyReadableFile {
8893
// `Option<Arc<Mutex<BufReader>>>`.
8994
fn close(&self) {}
9095

96+
#[getter]
97+
fn meta(&self) -> PyObjectMeta {
98+
self.meta.clone().into()
99+
}
100+
91101
#[pyo3(signature = (size = None, /))]
92102
fn read<'py>(&'py self, py: Python<'py>, size: Option<usize>) -> PyResult<PyObject> {
93103
let reader = self.reader.clone();
@@ -163,6 +173,11 @@ impl PyReadableFile {
163173
true
164174
}
165175

176+
#[getter]
177+
fn size(&self) -> usize {
178+
self.meta.size
179+
}
180+
166181
fn tell<'py>(&'py self, py: Python<'py>) -> PyResult<PyObject> {
167182
let reader = self.reader.clone();
168183
if self.r#async {

obstore/src/list.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ impl AsRef<ObjectMeta> for PyObjectMeta {
3434
}
3535
}
3636

37+
impl From<ObjectMeta> for PyObjectMeta {
38+
fn from(value: ObjectMeta) -> Self {
39+
Self(value)
40+
}
41+
}
42+
3743
impl<'py> IntoPyObject<'py> for PyObjectMeta {
3844
type Target = PyDict;
3945
type Output = Bound<'py, PyDict>;

0 commit comments

Comments
 (0)