11use crate :: { Error , Json , Result } ;
2- use pyo3:: { prelude:: * , types:: PyDict } ;
2+ use geojson:: Geometry ;
3+ use pyo3:: prelude:: * ;
4+ use pyo3:: { exceptions:: PyValueError , types:: PyDict , Bound , FromPyObject , PyErr , PyResult } ;
5+ use stac:: Bbox ;
36use stac:: Format ;
4- use stac_api:: {
5- python:: { StringOrDict , StringOrList } ,
6- Search ,
7- } ;
7+ use stac_api:: { Fields , Filter , Items , Search , Sortby } ;
88
99#[ pyfunction]
1010#[ pyo3( signature = ( href, * , intersects=None , ids=None , collections=None , max_items=None , limit=None , bbox=None , datetime=None , include=None , exclude=None , sortby=None , filter=None , query=None , use_duckdb=None , * * kwargs) ) ]
@@ -21,13 +21,13 @@ pub fn search<'py>(
2121 datetime : Option < String > ,
2222 include : Option < StringOrList > ,
2323 exclude : Option < StringOrList > ,
24- sortby : Option < StringOrList > ,
24+ sortby : Option < PySortby < ' py > > ,
2525 filter : Option < StringOrDict > ,
2626 query : Option < Bound < ' py , PyDict > > ,
2727 use_duckdb : Option < bool > ,
2828 kwargs : Option < Bound < ' _ , PyDict > > ,
2929) -> PyResult < Bound < ' py , PyAny > > {
30- let search = stac_api :: python :: search (
30+ let search = build (
3131 intersects,
3232 ids,
3333 collections,
@@ -72,15 +72,15 @@ pub fn search_to<'py>(
7272 datetime : Option < String > ,
7373 include : Option < StringOrList > ,
7474 exclude : Option < StringOrList > ,
75- sortby : Option < StringOrList > ,
75+ sortby : Option < PySortby < ' py > > ,
7676 filter : Option < StringOrDict > ,
7777 query : Option < Bound < ' py , PyDict > > ,
7878 format : Option < String > ,
7979 options : Option < Vec < ( String , String ) > > ,
8080 use_duckdb : Option < bool > ,
8181 kwargs : Option < Bound < ' _ , PyDict > > ,
8282) -> PyResult < Bound < ' py , PyAny > > {
83- let search = stac_api :: python :: search (
83+ let search = build (
8484 intersects,
8585 ids,
8686 collections,
@@ -150,3 +150,144 @@ async fn search_api(
150150 let value = stac_api:: client:: search ( & href, search, max_items) . await ?;
151151 Ok ( value)
152152}
153+
154+ /// Creates a [Search] from Python arguments.
155+ #[ allow( clippy:: too_many_arguments) ]
156+ pub fn build < ' py > (
157+ intersects : Option < StringOrDict < ' py > > ,
158+ ids : Option < StringOrList > ,
159+ collections : Option < StringOrList > ,
160+ limit : Option < u64 > ,
161+ bbox : Option < Vec < f64 > > ,
162+ datetime : Option < String > ,
163+ include : Option < StringOrList > ,
164+ exclude : Option < StringOrList > ,
165+ sortby : Option < PySortby < ' py > > ,
166+ filter : Option < StringOrDict < ' py > > ,
167+ query : Option < Bound < ' py , PyDict > > ,
168+ kwargs : Option < Bound < ' py , PyDict > > ,
169+ ) -> PyResult < Search > {
170+ let mut fields = Fields :: default ( ) ;
171+ if let Some ( include) = include {
172+ fields. include = include. into ( ) ;
173+ }
174+ if let Some ( exclude) = exclude {
175+ fields. exclude = exclude. into ( ) ;
176+ }
177+ let fields = if fields. include . is_empty ( ) && fields. exclude . is_empty ( ) {
178+ None
179+ } else {
180+ Some ( fields)
181+ } ;
182+ let query = query
183+ . map ( |query| pythonize:: depythonize ( & query) )
184+ . transpose ( ) ?;
185+ let bbox = bbox. map ( Bbox :: try_from) . transpose ( ) . map_err ( Error :: from) ?;
186+ let sortby: Vec < Sortby > = sortby
187+ . map ( |sortby| match sortby {
188+ PySortby :: ListOfDicts ( list) => list
189+ . into_iter ( )
190+ . map ( |d| pythonize:: depythonize ( & d) . map_err ( Error :: from) )
191+ . collect :: < Result < Vec < _ > > > ( ) ,
192+ PySortby :: ListOfStrings ( list) => list
193+ . into_iter ( )
194+ . map ( |s| Ok ( s. parse ( ) . unwrap ( ) ) ) // infallible
195+ . collect :: < Result < Vec < _ > > > ( ) ,
196+ PySortby :: String ( s) => Ok ( vec ! [ s. parse( ) . unwrap( ) ] ) ,
197+ } )
198+ . transpose ( ) ?
199+ . unwrap_or_default ( ) ;
200+ let filter = filter
201+ . map ( |filter| match filter {
202+ StringOrDict :: Dict ( cql_json) => pythonize:: depythonize ( & cql_json) . map ( Filter :: Cql2Json ) ,
203+ StringOrDict :: String ( cql2_text) => Ok ( Filter :: Cql2Text ( cql2_text) ) ,
204+ } )
205+ . transpose ( ) ?;
206+ let filter = filter
207+ . map ( |filter| filter. into_cql2_json ( ) )
208+ . transpose ( )
209+ . map_err ( Error :: from) ?;
210+ let mut items = Items {
211+ limit,
212+ bbox,
213+ datetime,
214+ query,
215+ fields,
216+ sortby,
217+ filter,
218+ ..Default :: default ( )
219+ } ;
220+ if let Some ( kwargs) = kwargs {
221+ items. additional_fields = pythonize:: depythonize ( & kwargs) ?;
222+ }
223+
224+ let intersects = intersects
225+ . map ( |intersects| match intersects {
226+ StringOrDict :: Dict ( json) => pythonize:: depythonize ( & json)
227+ . map_err ( PyErr :: from)
228+ . and_then ( |json| {
229+ Geometry :: from_json_object ( json)
230+ . map_err ( |err| PyValueError :: new_err ( err. to_string ( ) ) )
231+ } ) ,
232+ StringOrDict :: String ( s) => s
233+ . parse :: < Geometry > ( )
234+ . map_err ( |err| PyValueError :: new_err ( err. to_string ( ) ) ) ,
235+ } )
236+ . transpose ( ) ?;
237+ let ids = ids. map ( |ids| ids. into ( ) ) . unwrap_or_default ( ) ;
238+ let collections = collections. map ( |ids| ids. into ( ) ) . unwrap_or_default ( ) ;
239+ Ok ( Search {
240+ items,
241+ intersects,
242+ ids,
243+ collections,
244+ } )
245+ }
246+
247+ /// A string or dictionary.
248+ ///
249+ /// Used for the CQL2 filter argument and for intersects.
250+ #[ derive( Debug , FromPyObject ) ]
251+ pub enum StringOrDict < ' py > {
252+ /// Text
253+ String ( String ) ,
254+
255+ /// Json
256+ Dict ( Bound < ' py , PyDict > ) ,
257+ }
258+
259+ /// A string or a list.
260+ ///
261+ /// Used for collections, ids, etc.
262+ #[ derive( Debug , FromPyObject ) ]
263+ pub enum StringOrList {
264+ /// A string.
265+ String ( String ) ,
266+
267+ /// A list.
268+ List ( Vec < String > ) ,
269+ }
270+
271+ /// A sortby structure.
272+ ///
273+ /// This can be a string, a list of strings, or a list of dictionaries.
274+ #[ derive( Debug , FromPyObject ) ]
275+ pub enum PySortby < ' py > {
276+ /// A string.
277+ String ( String ) ,
278+
279+ /// A list.
280+ ListOfStrings ( Vec < String > ) ,
281+
282+ /// A list.
283+ ListOfDicts ( Vec < Bound < ' py , PyDict > > ) ,
284+ }
285+
286+ impl From < StringOrList > for Vec < String > {
287+ fn from ( value : StringOrList ) -> Vec < String > {
288+ match value {
289+ StringOrList :: List ( list) => list,
290+ StringOrList :: String ( s) => vec ! [ s] ,
291+ }
292+ }
293+ }
0 commit comments