stac_api/
python.rs

1//! Functions for convert [pyo3] objects into **stac-api** structures.
2
3use crate::{Error, Fields, Filter, Items, Search, Sortby};
4use geojson::Geometry;
5use pyo3::{
6    exceptions::{PyException, PyValueError},
7    types::PyDict,
8    Bound, FromPyObject, PyErr, PyResult,
9};
10use stac::Bbox;
11
12/// Creates a [Search] from Python arguments.
13#[allow(clippy::too_many_arguments)]
14pub fn search<'py>(
15    intersects: Option<StringOrDict<'py>>,
16    ids: Option<StringOrList>,
17    collections: Option<StringOrList>,
18    limit: Option<u64>,
19    bbox: Option<Vec<f64>>,
20    datetime: Option<String>,
21    include: Option<StringOrList>,
22    exclude: Option<StringOrList>,
23    sortby: Option<StringOrList>,
24    filter: Option<StringOrDict<'py>>,
25    query: Option<Bound<'py, PyDict>>,
26    kwargs: Option<Bound<'py, PyDict>>,
27) -> PyResult<Search> {
28    let mut fields = Fields::default();
29    if let Some(include) = include {
30        fields.include = include.into();
31    }
32    if let Some(exclude) = exclude {
33        fields.exclude = exclude.into();
34    }
35    let fields = if fields.include.is_empty() && fields.exclude.is_empty() {
36        None
37    } else {
38        Some(fields)
39    };
40    let query = query
41        .map(|query| pythonize::depythonize(&query))
42        .transpose()?;
43    let bbox = bbox.map(Bbox::try_from).transpose().map_err(Error::from)?;
44    let sortby = sortby
45        .map(|sortby| {
46            Vec::<String>::from(sortby)
47                .into_iter()
48                .map(|s| s.parse::<Sortby>().unwrap()) // the parse is infallible
49                .collect::<Vec<_>>()
50        })
51        .unwrap_or_default();
52    let filter = filter
53        .map(|filter| match filter {
54            StringOrDict::Dict(cql_json) => pythonize::depythonize(&cql_json).map(Filter::Cql2Json),
55            StringOrDict::String(cql2_text) => Ok(Filter::Cql2Text(cql2_text)),
56        })
57        .transpose()?;
58    let filter = filter
59        .map(|filter| filter.into_cql2_json())
60        .transpose()
61        .map_err(Error::from)?;
62    let mut items = Items {
63        limit,
64        bbox,
65        datetime,
66        query,
67        fields,
68        sortby,
69        filter,
70        ..Default::default()
71    };
72    if let Some(kwargs) = kwargs {
73        items.additional_fields = pythonize::depythonize(&kwargs)?;
74    }
75
76    let intersects = intersects
77        .map(|intersects| match intersects {
78            StringOrDict::Dict(json) => pythonize::depythonize(&json)
79                .map_err(PyErr::from)
80                .and_then(|json| {
81                    Geometry::from_json_object(json)
82                        .map_err(|err| PyValueError::new_err(err.to_string()))
83                }),
84            StringOrDict::String(s) => s
85                .parse::<Geometry>()
86                .map_err(|err| PyValueError::new_err(err.to_string())),
87        })
88        .transpose()?;
89    let ids = ids.map(|ids| ids.into()).unwrap_or_default();
90    let collections = collections.map(|ids| ids.into()).unwrap_or_default();
91    Ok(Search {
92        items,
93        intersects,
94        ids,
95        collections,
96    })
97}
98
99/// A string or dictionary.
100///
101/// Used for the CQL2 filter argument and for intersects.
102#[derive(Debug, FromPyObject)]
103pub enum StringOrDict<'py> {
104    /// Text
105    String(String),
106
107    /// Json
108    Dict(Bound<'py, PyDict>),
109}
110
111/// A string or a list.
112///
113/// Used for collections, ids, etc.
114#[derive(Debug, FromPyObject)]
115pub enum StringOrList {
116    /// A string.
117    String(String),
118
119    /// A list.
120    List(Vec<String>),
121}
122
123impl From<StringOrList> for Vec<String> {
124    fn from(value: StringOrList) -> Vec<String> {
125        match value {
126            StringOrList::List(list) => list,
127            StringOrList::String(s) => vec![s],
128        }
129    }
130}
131
132impl From<Error> for PyErr {
133    fn from(value: Error) -> Self {
134        PyException::new_err(value.to_string())
135    }
136}