Skip to content

Commit cb88ed2

Browse files
authored
fix: Fix path parsing for copy, rename (#672)
* fix: Fix path parsing for copy, list, rename * Add tests * bump pyo3 version
1 parent 8874812 commit cb88ed2

10 files changed

Lines changed: 138 additions & 40 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
*.dylib
2+
*.dylib.yml
3+
Info.plist
14
*.so
25
*.whl
36

Cargo.lock

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

obstore/src/copy.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{future::Future, pin::Pin};
33
use object_store::ObjectStoreExt;
44
use pyo3::prelude::*;
55
use pyo3_async_runtimes::tokio::get_runtime;
6-
use pyo3_object_store::{PyObjectStore, PyObjectStoreError, PyObjectStoreResult};
6+
use pyo3_object_store::{PyObjectStore, PyObjectStoreError, PyObjectStoreResult, PyPath};
77

88
use crate::utils::PyNone;
99

@@ -12,8 +12,8 @@ use crate::utils::PyNone;
1212
pub(crate) fn copy(
1313
py: Python,
1414
store: PyObjectStore,
15-
from_: String,
16-
to: String,
15+
from_: PyPath,
16+
to: PyPath,
1717
overwrite: bool,
1818
) -> PyObjectStoreResult<()> {
1919
let runtime = get_runtime();
@@ -35,8 +35,8 @@ pub(crate) fn copy(
3535
pub(crate) fn copy_async(
3636
py: Python,
3737
store: PyObjectStore,
38-
from_: String,
39-
to: String,
38+
from_: PyPath,
39+
to: PyPath,
4040
overwrite: bool,
4141
) -> PyResult<Bound<PyAny>> {
4242
let from_ = from_.into();

obstore/src/get.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ pub(crate) fn get(
346346
) -> PyObjectStoreResult<PyGetResult> {
347347
let runtime = get_runtime();
348348
py.detach(|| {
349-
let path = &path.as_ref();
349+
let path = path.as_ref();
350350
let fut = if let Some(options) = options {
351351
store.as_ref().get_opts(path, options.into())
352352
} else {
@@ -366,7 +366,7 @@ pub(crate) fn get_async(
366366
options: Option<PyGetOptions>,
367367
) -> PyResult<Bound<PyAny>> {
368368
pyo3_async_runtimes::tokio::future_into_py(py, async move {
369-
let path = &path.as_ref();
369+
let path = path.as_ref();
370370
let fut = if let Some(options) = options {
371371
store.as_ref().get_opts(path, options.into())
372372
} else {

obstore/src/list.rs

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use arrow::datatypes::{DataType, Field, Schema, TimeUnit};
88
use futures::stream::{BoxStream, Fuse};
99
use futures::StreamExt;
1010
use indexmap::IndexMap;
11-
use object_store::path::Path;
1211
use object_store::{ListResult, ObjectMeta, ObjectStore};
1312
use pyo3::exceptions::{PyImportError, PyStopAsyncIteration, PyStopIteration};
1413
use pyo3::prelude::*;
@@ -17,7 +16,7 @@ use pyo3::{intern, IntoPyObjectExt};
1716
use pyo3_arrow::export::{Arro3RecordBatch, Arro3Table};
1817
use pyo3_arrow::PyTable;
1918
use pyo3_async_runtimes::tokio::get_runtime;
20-
use pyo3_object_store::{PyObjectStore, PyObjectStoreError, PyObjectStoreResult};
19+
use pyo3_object_store::{PyObjectStore, PyObjectStoreError, PyObjectStoreResult, PyPath};
2120
use tokio::sync::Mutex;
2221

2322
pub(crate) struct PyObjectMeta(ObjectMeta);
@@ -353,8 +352,8 @@ impl<'py> IntoPyObject<'py> for PyListResult {
353352
pub(crate) fn list(
354353
py: Python,
355354
store: PyObjectStore,
356-
prefix: Option<String>,
357-
offset: Option<String>,
355+
prefix: Option<PyPath>,
356+
offset: Option<PyPath>,
358357
chunk_size: usize,
359358
return_arrow: bool,
360359
) -> PyObjectStoreResult<PyListStream> {
@@ -373,7 +372,7 @@ pub(crate) fn list(
373372
let store = store.into_inner().clone();
374373
let prefix = prefix.map(|s| s.into());
375374
let stream = if let Some(offset) = offset {
376-
store.list_with_offset(prefix.as_ref(), &offset.into())
375+
store.list_with_offset(prefix.as_ref(), offset.as_ref())
377376
} else {
378377
store.list(prefix.as_ref())
379378
};
@@ -385,14 +384,14 @@ pub(crate) fn list(
385384
pub(crate) fn list_with_delimiter(
386385
py: Python,
387386
store: PyObjectStore,
388-
prefix: Option<String>,
387+
prefix: Option<PyPath>,
389388
return_arrow: bool,
390389
) -> PyObjectStoreResult<PyListResult> {
391390
let runtime = get_runtime();
392391
py.detach(|| {
393392
let out = runtime.block_on(list_with_delimiter_materialize(
394393
store.into_inner(),
395-
prefix.map(|s| s.into()).as_ref(),
394+
prefix.as_ref(),
396395
return_arrow,
397396
))?;
398397
Ok::<_, PyObjectStoreError>(out)
@@ -404,25 +403,24 @@ pub(crate) fn list_with_delimiter(
404403
pub(crate) fn list_with_delimiter_async(
405404
py: Python,
406405
store: PyObjectStore,
407-
prefix: Option<String>,
406+
prefix: Option<PyPath>,
408407
return_arrow: bool,
409408
) -> PyResult<Bound<PyAny>> {
410409
pyo3_async_runtimes::tokio::future_into_py(py, async move {
411-
let out = list_with_delimiter_materialize(
412-
store.into_inner(),
413-
prefix.map(|s| s.into()).as_ref(),
414-
return_arrow,
415-
)
416-
.await?;
410+
let out =
411+
list_with_delimiter_materialize(store.into_inner(), prefix.as_ref(), return_arrow)
412+
.await?;
417413
Ok(out)
418414
})
419415
}
420416

421417
async fn list_with_delimiter_materialize(
422418
store: Arc<dyn ObjectStore>,
423-
prefix: Option<&Path>,
419+
prefix: Option<&PyPath>,
424420
return_arrow: bool,
425421
) -> PyObjectStoreResult<PyListResult> {
426-
let list_result = store.list_with_delimiter(prefix).await?;
422+
let list_result = store
423+
.list_with_delimiter(prefix.map(|s| s.as_ref()))
424+
.await?;
427425
Ok(PyListResult::new(list_result, return_arrow))
428426
}

obstore/src/rename.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{future::Future, pin::Pin};
33
use object_store::ObjectStoreExt;
44
use pyo3::prelude::*;
55
use pyo3_async_runtimes::tokio::get_runtime;
6-
use pyo3_object_store::{PyObjectStore, PyObjectStoreError, PyObjectStoreResult};
6+
use pyo3_object_store::{PyObjectStore, PyObjectStoreError, PyObjectStoreResult, PyPath};
77

88
use crate::utils::PyNone;
99

@@ -12,8 +12,8 @@ use crate::utils::PyNone;
1212
pub(crate) fn rename(
1313
py: Python,
1414
store: PyObjectStore,
15-
from_: String,
16-
to: String,
15+
from_: PyPath,
16+
to: PyPath,
1717
overwrite: bool,
1818
) -> PyObjectStoreResult<()> {
1919
let runtime = get_runtime();
@@ -35,8 +35,8 @@ pub(crate) fn rename(
3535
pub(crate) fn rename_async(
3636
py: Python,
3737
store: PyObjectStore,
38-
from_: String,
39-
to: String,
38+
from_: PyPath,
39+
to: PyPath,
4040
overwrite: bool,
4141
) -> PyResult<Bound<PyAny>> {
4242
let from_ = from_.into();

tests/test_copy.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from obstore.store import MemoryStore
2+
3+
4+
def test_copy_non_ascii():
5+
store = MemoryStore()
6+
7+
name1 = "café.txt"
8+
name2 = "ümlaut.txt"
9+
name3 = "こんにちは世界.txt"
10+
name4 = "你好世界.txt"
11+
12+
store.put(name1, b"foo")
13+
store.put(name3, b"bar")
14+
15+
store.copy(name1, name2)
16+
store.copy(name3, name4)
17+
18+
result = store.list().collect()
19+
assert len(result) == 4
20+
assert result[0]["path"] == name1
21+
assert result[1]["path"] == name2
22+
assert result[2]["path"] == name3
23+
assert result[3]["path"] == name4

tests/test_head.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from obstore.store import MemoryStore
2+
3+
4+
def test_head_non_ascii():
5+
store = MemoryStore()
6+
7+
name1 = "café.txt"
8+
name2 = "ümlaut.txt"
9+
name3 = "こんにちは世界.txt"
10+
name4 = "你好世界.txt"
11+
12+
store.put(name1, b"foo")
13+
store.put(name2, b"bar")
14+
store.put(name3, b"baz")
15+
store.put(name4, b"qux")
16+
17+
assert store.head(name1)["path"] == name1
18+
assert store.head(name2)["path"] == name2
19+
assert store.head(name3)["path"] == name3
20+
assert store.head(name4)["path"] == name4

tests/test_list.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,27 @@ def test_list():
1111
store.put("file2.txt", b"bar")
1212
store.put("file3.txt", b"baz")
1313

14-
stream = store.list()
15-
result = stream.collect()
14+
result = store.list().collect()
1615
assert len(result) == 3
1716

1817

18+
def test_list_non_ascii():
19+
store = MemoryStore()
20+
21+
name1 = "café.txt"
22+
name2 = "ümlaut.txt"
23+
name3 = "こんにちは世界.txt"
24+
store.put(name1, b"foo")
25+
store.put(name2, b"bar")
26+
store.put(name3, b"baz")
27+
28+
result = store.list().collect()
29+
assert len(result) == 3
30+
assert result[0]["path"] == name1
31+
assert result[1]["path"] == name2
32+
assert result[2]["path"] == name3
33+
34+
1935
def test_list_as_arrow():
2036
store = MemoryStore()
2137

@@ -37,6 +53,23 @@ def test_list_as_arrow():
3753
assert batch.num_rows == 100
3854

3955

56+
def test_list_non_ascii_arrow():
57+
store = MemoryStore()
58+
59+
name1 = "café.txt"
60+
name2 = "ümlaut.txt"
61+
name3 = "こんにちは世界.txt"
62+
store.put(name1, b"foo")
63+
store.put(name2, b"bar")
64+
store.put(name3, b"baz")
65+
66+
result = store.list(return_arrow=True).collect()
67+
assert result.num_rows == 3
68+
assert result["path"][0].as_py() == name1
69+
assert result["path"][1].as_py() == name2
70+
assert result["path"][2].as_py() == name3
71+
72+
4073
@pytest.mark.asyncio
4174
async def test_list_stream_async():
4275
store = MemoryStore()

tests/test_rename.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from obstore.store import MemoryStore
2+
3+
4+
def test_rename_non_ascii():
5+
store = MemoryStore()
6+
7+
name1 = "café.txt"
8+
name2 = "ümlaut.txt"
9+
name3 = "こんにちは世界.txt"
10+
name4 = "你好世界.txt"
11+
12+
store.put(name1, b"foo")
13+
store.put(name3, b"bar")
14+
15+
store.rename(name1, name2)
16+
store.rename(name3, name4)
17+
18+
result = store.list().collect()
19+
assert len(result) == 2
20+
assert result[0]["path"] == name2
21+
assert result[1]["path"] == name4

0 commit comments

Comments
 (0)