Skip to content

Commit

Permalink
feat(python): add search
Browse files Browse the repository at this point in the history
  • Loading branch information
gadomski committed Sep 14, 2024
1 parent acfaff4 commit d12f3e8
Show file tree
Hide file tree
Showing 23 changed files with 754 additions and 227 deletions.
11 changes: 7 additions & 4 deletions .github/workflows/stacrs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
push:
tags:
- 'python-*'
pull_request:
paths:
- python/**
workflow_dispatch:

permissions:
Expand All @@ -25,7 +28,7 @@ jobs:
- name: Install dev requirements
run: pip install -r python/requirements-dev.txt
- name: Build
run: maturin build --manifest-path python/Cargo.toml --out dist
run: maturin build --manifest-path python/Cargo.toml --out dist -F geoparquet
- name: Install stacrs
run: pip install stacrs --find-links dist --no-index
- name: Check
Expand Down Expand Up @@ -60,7 +63,7 @@ jobs:
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter --manifest-path python/Cargo.toml
args: --release --out dist --find-interpreter --manifest-path python/Cargo.toml -F geoparquet
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -120,7 +123,7 @@ jobs:
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter --manifest-path python/Cargo.toml
args: --release --out dist --find-interpreter --manifest-path python/Cargo.toml -F geoparquet
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -148,7 +151,7 @@ jobs:
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter --manifest-path python/Cargo.toml
args: --release --out dist --find-interpreter --manifest-path python/Cargo.toml -F geoparquet
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v4
Expand Down
1 change: 1 addition & 0 deletions api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added

- `Client` (from now-defunkt **stac-async**) ([#372](https://github.com/stac-utils/stac-rs/pull/372))
- `BlockingClient` ([#387](https://github.com/stac-utils/stac-rs/pull/387))

## [0.5.0] - 2024-09-05

Expand Down
68 changes: 67 additions & 1 deletion api/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! A STAC API client.

use crate::{Error, GetItems, Item, ItemCollection, Items, Result, Search, UrlBuilder};
use async_stream::try_stream;
use futures::{pin_mut, Stream, StreamExt};
Expand All @@ -6,7 +8,9 @@ use reqwest::{header::HeaderMap, IntoUrl, Method, StatusCode};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::{Map, Value};
use stac::{Collection, Href, Link, Links};
use std::pin::Pin;
use tokio::{
runtime::{Builder, Runtime},
sync::mpsc::{self, error::SendError},
task::JoinHandle,
};
Expand All @@ -21,6 +25,17 @@ pub struct Client {
url_builder: UrlBuilder,
}

/// A client for interacting with STAC APIs without async.
#[derive(Debug)]
pub struct BlockingClient(Client);

/// A blocking iterator over items.
#[allow(missing_debug_implementations)]
pub struct BlockingIterator {
runtime: Runtime,
stream: Pin<Box<dyn Stream<Item = Result<Item>>>>,
}

impl Client {
/// Creates a new API client.
///
Expand Down Expand Up @@ -127,12 +142,12 @@ impl Client {
///
/// let client = Client::new("https://planetarycomputer.microsoft.com/api/stac/v1").unwrap();
/// let mut search = Search { collections: Some(vec!["sentinel-2-l2a".to_string()]), ..Default::default() };
/// search.items.limit = Some(1);
/// # tokio_test::block_on(async {
/// let items: Vec<_> = client
/// .search(search)
/// .await
/// .unwrap()
/// .take(1)
/// .map(|result| result.unwrap())
/// .collect()
/// .await;
Expand Down Expand Up @@ -226,6 +241,57 @@ impl Client {
}
}

impl BlockingClient {
/// Creates a new blocking client.
///
/// # Examples
///
/// ```
/// use stac_api::BlockingClient;
///
/// let client = BlockingClient::new("https://planetarycomputer.microsoft.com/api/stac/vi").unwrap();
/// ```
pub fn new(url: &str) -> Result<BlockingClient> {
Client::new(url).map(Self)
}

/// Searches an API, returning an iterable of items.
///
/// To prevent fetching _all_ the items (which might be a lot), it is recommended to pass a `max_items`.
///
/// # Examples
///
/// ```no_run
/// use stac_api::{Search, BlockingClient};
///
/// let client = BlockingClient::new("https://planetarycomputer.microsoft.com/api/stac/v1").unwrap();
/// let mut search = Search { collections: Some(vec!["sentinel-2-l2a".to_string()]), ..Default::default() };
/// let items: Vec<_> = client
/// .search(search)
/// .unwrap()
/// .map(|result| result.unwrap())
/// .take(1)
/// .collect();
/// assert_eq!(items.len(), 1);
/// ```
pub fn search(&self, search: Search) -> Result<BlockingIterator> {
let runtime = Builder::new_current_thread().enable_all().build()?;
let stream = runtime.block_on(async move { self.0.search(search).await })?;
Ok(BlockingIterator {
runtime,
stream: Box::pin(stream),
})
}
}

impl Iterator for BlockingIterator {
type Item = Result<Item>;

fn next(&mut self) -> Option<Self::Item> {
self.runtime.block_on(self.stream.next())
}
}

fn stream_items(
client: Client,
page: ItemCollection,
Expand Down
5 changes: 5 additions & 0 deletions api/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ pub enum Error {
#[cfg(feature = "client")]
InvalidMethod(#[from] http::method::InvalidMethod),

/// [std::io::Error]
#[error(transparent)]
#[cfg(feature = "client")]
Io(#[from] std::io::Error),

/// [tokio::task::JoinError]
#[error(transparent)]
#[cfg(feature = "client")]
Expand Down
9 changes: 9 additions & 0 deletions api/src/filter.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::{convert::Infallible, str::FromStr};

use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

Expand All @@ -20,6 +22,13 @@ impl Default for Filter {
}
}

impl FromStr for Filter {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Filter::Cql2Text(s.to_string()))
}
}

#[cfg(test)]
mod tests {
use super::Filter;
Expand Down
4 changes: 2 additions & 2 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
)]

#[cfg(feature = "client")]
mod client;
pub mod client;
mod collections;
mod conformance;
mod error;
Expand All @@ -78,7 +78,7 @@ mod sort;
mod url_builder;

#[cfg(feature = "client")]
pub use client::Client;
pub use client::{BlockingClient, Client};
pub use {
collections::Collections,
conformance::{
Expand Down
1 change: 1 addition & 0 deletions python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]

- `migrate_href` ([#334](https://github.com/stac-utils/stac-rs/pull/334))
- `search` and `search_to` ([#387](https://github.com/stac-utils/stac-rs/pull/387))

## [0.0.3] - 2024-08-29

Expand Down
1 change: 1 addition & 0 deletions python/CODE_OF_CONDUCT
10 changes: 9 additions & 1 deletion python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ publish = false
name = "stacrs"
crate-type = ["cdylib"]

[features]
geoparquet = ["stac/geoparquet-compression"]

[dependencies]
geojson = "0.24"
pyo3 = "0.22"
pythonize = "0.22"
stac = { path = "../core", features = ["reqwest"] }
serde = "1"
serde_json = "1"
stac = { path = "../core", features = ["reqwest", "object-store-full"] }
stac-api = { path = "../api", features = ["client"] }
stac-validate = { path = "../validate" }
tokio = { version = "1", features = ["rt"] }
10 changes: 10 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ Then:
```python
import stacrs

# Searches a STAC API
items = stacrs.search(
"https://landsatlook.usgs.gov/stac-server",
collections="landsat-c2l2-sr",
intersects={"type": "Point", "coordinates": [-105.119, 40.173]},
sortby="-properties.datetime",
max_items=1,
)

# Validates a href using json-schema
stacrs.validate_href("https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/simple-item.json")
```

Expand Down
1 change: 1 addition & 0 deletions python/docs/CODE_OF_CONDUCT
7 changes: 2 additions & 5 deletions python/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

API documentation for **stacrs**.

## Migrate

::: stacrs.migrate
::: stacrs.migrate_href

## Validate

::: stacrs.search
::: stacrs.search_to
::: stacrs.validate
::: stacrs.validate_href
64 changes: 0 additions & 64 deletions python/docs/index.md

This file was deleted.

1 change: 1 addition & 0 deletions python/docs/index.md
1 change: 1 addition & 0 deletions python/requirements-dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ maturin
mypy
ruff
pytest
stac-geoparquet
Loading

0 comments on commit d12f3e8

Please sign in to comment.