Skip to content

Commit d708d3f

Browse files
committed
feat: add walk
1 parent 3c7d494 commit d708d3f

File tree

7 files changed

+76
-37
lines changed

7 files changed

+76
-37
lines changed

Cargo.lock

Lines changed: 27 additions & 9 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
@@ -39,6 +39,7 @@ stac-cli = { git = "https://github.com/stac-utils/stac-rs", features = [
3939
stac-duckdb = { git = "https://github.com/stac-utils/stac-rs" }
4040
thiserror = "2.0.11"
4141
tokio = { version = "1.43.0", features = ["rt-multi-thread"] }
42+
pyo3-log = "0.12.1"
4243

4344
[patch.crates-io]
4445
duckdb = { git = "https://github.com/duckdb/duckdb-rs", rev = "5eeb1f01c278790ce1e2d24045f0096e9e2528e4" }

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type Result<T> = std::result::Result<T, Error>;
1818

1919
#[pymodule]
2020
fn stacrs(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
21+
pyo3_log::init();
22+
2123
m.add("StacrsError", py.get_type::<error::StacrsError>())?;
2224

2325
m.add_class::<duckdb::DuckdbClient>()?;

src/read.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
use crate::{Error, Json};
22
use pyo3::{pyfunction, types::PyAny, Bound, PyResult, Python};
3-
use stac::{Format, Value};
3+
use stac::{Format, Link, Links, SelfHref, Value};
44

55
#[pyfunction]
6-
#[pyo3(signature = (href, *, format=None, options=None))]
6+
#[pyo3(signature = (href, *, format=None, options=None, set_self_link=true))]
77
pub fn read(
88
py: Python<'_>,
99
href: String,
1010
format: Option<String>,
1111
options: Option<Vec<(String, String)>>,
12+
set_self_link: bool,
1213
) -> PyResult<Bound<'_, PyAny>> {
1314
let format = format
1415
.and_then(|f| f.parse::<Format>().ok())
1516
.or_else(|| Format::infer_from_href(&href))
1617
.unwrap_or_default();
1718
let options = options.unwrap_or_default();
1819
pyo3_async_runtimes::tokio::future_into_py(py, async move {
19-
let value = format
20+
let mut value = format
2021
.get_opts::<Value, _, _, _>(href, options)
2122
.await
2223
.map_err(Error::from)?;
24+
if set_self_link {
25+
if let Some(href) = value.self_href().cloned() {
26+
value.set_link(Link::self_(href));
27+
}
28+
}
2329
Ok(Json(value))
2430
})
2531
}

src/walk.rs

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,55 @@
1-
use std::collections::VecDeque;
2-
31
use crate::{Error, Json, Result};
42
use pyo3::{
53
exceptions::PyStopAsyncIteration, pyclass, pyfunction, pymethods, types::PyDict, Bound, Py,
64
PyAny, PyResult, Python,
75
};
8-
use stac::{Container, Node, Value};
6+
use stac::{Container, Item, Links, Node, SelfHref, Value};
7+
use std::collections::VecDeque;
8+
use std::sync::Arc;
9+
use tokio::sync::Mutex;
910

1011
#[pyfunction]
1112
pub fn walk(container: Bound<'_, PyDict>) -> Result<Walk> {
12-
let value: Value = pythonize::depythonize(&container)?;
13+
let mut value: Value = pythonize::depythonize(&container)?;
14+
if let Some(link) = value.link("self").cloned() {
15+
*value.self_href_mut() = Some(link.href);
16+
}
1317
let container: Container = value.try_into()?;
1418
let node = Node::from(container);
1519
let mut walks = VecDeque::new();
1620
walks.push_back(node);
17-
Ok(Walk(walks))
21+
Ok(Walk(Arc::new(Mutex::new(walks))))
1822
}
1923

2024
#[pyclass]
21-
pub struct Walk(VecDeque<Node>);
25+
pub struct Walk(Arc<Mutex<VecDeque<Node>>>);
2226

2327
#[pymethods]
2428
impl Walk {
2529
fn __aiter__(slf: Py<Self>) -> Py<Self> {
2630
slf
2731
}
2832

29-
fn __anext__<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
30-
if let Some(node) = self.0.pop_front() {
31-
pyo3_async_runtimes::tokio::future_into_py(py, async move {
32-
let node = node.resolve().await.map_err(Error::from)?;
33-
let mut children = Vec::with_capacity(node.children.len());
34-
for child in node.children {
35-
children.push(Json(child.value.clone()));
36-
self.0.push_back(child);
37-
}
38-
let items: Vec<_> = node.items.iter().map(|item| Json(item.clone())).collect();
39-
Ok((Json(node.value), children, items))
40-
})
41-
} else {
42-
Err(PyStopAsyncIteration::new_err("walk complete"))
33+
fn __anext__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
34+
let nodes = self.0.clone();
35+
pyo3_async_runtimes::tokio::future_into_py(py, async move { next_walk(nodes).await })
36+
}
37+
}
38+
39+
type WalkStep = (Value, Vec<Container>, VecDeque<Item>);
40+
41+
async fn next_walk(nodes: Arc<Mutex<VecDeque<Node>>>) -> PyResult<Json<WalkStep>> {
42+
let mut nodes = nodes.lock().await;
43+
if let Some(node) = nodes.pop_front() {
44+
let mut node = node.resolve().await.map_err(Error::from)?;
45+
let items = std::mem::take(&mut node.items);
46+
let mut children = Vec::with_capacity(node.children.len());
47+
for child in node.children {
48+
children.push(child.value.clone());
49+
nodes.push_back(child);
4350
}
51+
Ok(Json((node.value.into(), children, items)))
52+
} else {
53+
Err(PyStopAsyncIteration::new_err("done walking"))
4454
}
4555
}

stacrs.pyi

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,16 +198,18 @@ async def read(
198198
*,
199199
format: str | None = None,
200200
options: list[tuple[str, str]] | None = None,
201+
set_self_link: bool = True,
201202
) -> dict[str, Any]:
202203
"""
203204
Reads STAC from a href.
204205
205206
Args:
206-
href (str): The href to write to
207-
format (str | None): The input format. If not provided, will be inferred
207+
href: The href to write to
208+
format: The input format. If not provided, will be inferred
208209
from the href's extension.
209-
options (list[tuple[str, str]] | None): Options for configuring an
210+
options: Options for configuring an
210211
object store, e.g. your AWS credentials.
212+
set_self_link: If True, set the `self` link to the value of `href`.
211213
212214
Returns:
213215
The STAC value

tests/test_walk.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ async def test_walk(examples: Path) -> None:
1111
all_children.extend(children)
1212
all_items.extend(items)
1313

14-
assert len(all_children) == 2
15-
assert len(all_items) == 3
14+
assert len(all_children) == 3
15+
assert len(all_items) == 2

0 commit comments

Comments
 (0)