1
1
use 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 ;
3
6
use stac:: Format ;
4
- use stac_api:: {
5
- python:: { StringOrDict , StringOrList } ,
6
- Search ,
7
- } ;
7
+ use stac_api:: { Fields , Filter , Items , Search , Sortby } ;
8
8
9
9
#[ pyfunction]
10
10
#[ 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>(
21
21
datetime : Option < String > ,
22
22
include : Option < StringOrList > ,
23
23
exclude : Option < StringOrList > ,
24
- sortby : Option < StringOrList > ,
24
+ sortby : Option < PySortby < ' py > > ,
25
25
filter : Option < StringOrDict > ,
26
26
query : Option < Bound < ' py , PyDict > > ,
27
27
use_duckdb : Option < bool > ,
28
28
kwargs : Option < Bound < ' _ , PyDict > > ,
29
29
) -> PyResult < Bound < ' py , PyAny > > {
30
- let search = stac_api :: python :: search (
30
+ let search = build (
31
31
intersects,
32
32
ids,
33
33
collections,
@@ -72,15 +72,15 @@ pub fn search_to<'py>(
72
72
datetime : Option < String > ,
73
73
include : Option < StringOrList > ,
74
74
exclude : Option < StringOrList > ,
75
- sortby : Option < StringOrList > ,
75
+ sortby : Option < PySortby < ' py > > ,
76
76
filter : Option < StringOrDict > ,
77
77
query : Option < Bound < ' py , PyDict > > ,
78
78
format : Option < String > ,
79
79
options : Option < Vec < ( String , String ) > > ,
80
80
use_duckdb : Option < bool > ,
81
81
kwargs : Option < Bound < ' _ , PyDict > > ,
82
82
) -> PyResult < Bound < ' py , PyAny > > {
83
- let search = stac_api :: python :: search (
83
+ let search = build (
84
84
intersects,
85
85
ids,
86
86
collections,
@@ -150,3 +150,144 @@ async fn search_api(
150
150
let value = stac_api:: client:: search ( & href, search, max_items) . await ?;
151
151
Ok ( value)
152
152
}
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