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