stac-fastapi-opensearch 6.3.0__py3-none-any.whl → 6.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stac_fastapi/opensearch/app.py +29 -10
- stac_fastapi/opensearch/database_logic.py +185 -65
- stac_fastapi/opensearch/version.py +1 -1
- {stac_fastapi_opensearch-6.3.0.dist-info → stac_fastapi_opensearch-6.4.0.dist-info}/METADATA +42 -5
- stac_fastapi_opensearch-6.4.0.dist-info/RECORD +10 -0
- stac_fastapi_opensearch-6.3.0.dist-info/RECORD +0 -10
- {stac_fastapi_opensearch-6.3.0.dist-info → stac_fastapi_opensearch-6.4.0.dist-info}/WHEEL +0 -0
- {stac_fastapi_opensearch-6.3.0.dist-info → stac_fastapi_opensearch-6.4.0.dist-info}/entry_points.txt +0 -0
- {stac_fastapi_opensearch-6.3.0.dist-info → stac_fastapi_opensearch-6.4.0.dist-info}/top_level.txt +0 -0
stac_fastapi/opensearch/app.py
CHANGED
|
@@ -28,7 +28,7 @@ from stac_fastapi.core.rate_limit import setup_rate_limit
|
|
|
28
28
|
from stac_fastapi.core.route_dependencies import get_route_dependencies
|
|
29
29
|
from stac_fastapi.core.session import Session
|
|
30
30
|
from stac_fastapi.core.utilities import get_bool_env
|
|
31
|
-
from stac_fastapi.extensions.core import (
|
|
31
|
+
from stac_fastapi.extensions.core import ( # CollectionSearchFilterExtension,
|
|
32
32
|
AggregationExtension,
|
|
33
33
|
CollectionSearchExtension,
|
|
34
34
|
FilterExtension,
|
|
@@ -39,6 +39,7 @@ from stac_fastapi.extensions.core import (
|
|
|
39
39
|
)
|
|
40
40
|
from stac_fastapi.extensions.core.fields import FieldsConformanceClasses
|
|
41
41
|
from stac_fastapi.extensions.core.filter import FilterConformanceClasses
|
|
42
|
+
from stac_fastapi.extensions.core.free_text import FreeTextConformanceClasses
|
|
42
43
|
from stac_fastapi.extensions.core.query import QueryConformanceClasses
|
|
43
44
|
from stac_fastapi.extensions.core.sort import SortConformanceClasses
|
|
44
45
|
from stac_fastapi.extensions.third_party import BulkTransactionExtension
|
|
@@ -55,7 +56,9 @@ logging.basicConfig(level=logging.INFO)
|
|
|
55
56
|
logger = logging.getLogger(__name__)
|
|
56
57
|
|
|
57
58
|
TRANSACTIONS_EXTENSIONS = get_bool_env("ENABLE_TRANSACTIONS_EXTENSIONS", default=True)
|
|
59
|
+
ENABLE_COLLECTIONS_SEARCH = get_bool_env("ENABLE_COLLECTIONS_SEARCH", default=True)
|
|
58
60
|
logger.info("TRANSACTIONS_EXTENSIONS is set to %s", TRANSACTIONS_EXTENSIONS)
|
|
61
|
+
logger.info("ENABLE_COLLECTIONS_SEARCH is set to %s", ENABLE_COLLECTIONS_SEARCH)
|
|
59
62
|
|
|
60
63
|
settings = OpensearchSettings()
|
|
61
64
|
session = Session.create_from_settings(settings)
|
|
@@ -69,14 +72,6 @@ filter_extension.conformance_classes.append(
|
|
|
69
72
|
FilterConformanceClasses.ADVANCED_COMPARISON_OPERATORS
|
|
70
73
|
)
|
|
71
74
|
|
|
72
|
-
# Adding collection search extension for compatibility with stac-auth-proxy
|
|
73
|
-
# (https://github.com/developmentseed/stac-auth-proxy)
|
|
74
|
-
# The extension is not fully implemented yet but is required for collection filtering support
|
|
75
|
-
collection_search_extension = CollectionSearchExtension()
|
|
76
|
-
collection_search_extension.conformance_classes.append(
|
|
77
|
-
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter"
|
|
78
|
-
)
|
|
79
|
-
|
|
80
75
|
aggregation_extension = AggregationExtension(
|
|
81
76
|
client=EsAsyncBaseAggregationClient(
|
|
82
77
|
database=database_logic, session=session, settings=settings
|
|
@@ -95,7 +90,6 @@ search_extensions = [
|
|
|
95
90
|
TokenPaginationExtension(),
|
|
96
91
|
filter_extension,
|
|
97
92
|
FreeTextExtension(),
|
|
98
|
-
collection_search_extension,
|
|
99
93
|
]
|
|
100
94
|
|
|
101
95
|
|
|
@@ -122,6 +116,27 @@ if TRANSACTIONS_EXTENSIONS:
|
|
|
122
116
|
|
|
123
117
|
extensions = [aggregation_extension] + search_extensions
|
|
124
118
|
|
|
119
|
+
# Create collection search extensions if enabled
|
|
120
|
+
if ENABLE_COLLECTIONS_SEARCH:
|
|
121
|
+
# Create collection search extensions
|
|
122
|
+
collection_search_extensions = [
|
|
123
|
+
# QueryExtension(conformance_classes=[QueryConformanceClasses.COLLECTIONS]),
|
|
124
|
+
SortExtension(conformance_classes=[SortConformanceClasses.COLLECTIONS]),
|
|
125
|
+
FieldsExtension(conformance_classes=[FieldsConformanceClasses.COLLECTIONS]),
|
|
126
|
+
# CollectionSearchFilterExtension(
|
|
127
|
+
# conformance_classes=[FilterConformanceClasses.COLLECTIONS]
|
|
128
|
+
# ),
|
|
129
|
+
FreeTextExtension(conformance_classes=[FreeTextConformanceClasses.COLLECTIONS]),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
# Initialize collection search with its extensions
|
|
133
|
+
collection_search_ext = CollectionSearchExtension.from_extensions(
|
|
134
|
+
collection_search_extensions
|
|
135
|
+
)
|
|
136
|
+
collections_get_request_model = collection_search_ext.GET
|
|
137
|
+
|
|
138
|
+
extensions.append(collection_search_ext)
|
|
139
|
+
|
|
125
140
|
database_logic.extensions = [type(ext).__name__ for ext in extensions]
|
|
126
141
|
|
|
127
142
|
post_request_model = create_post_request_model(search_extensions)
|
|
@@ -160,6 +175,10 @@ app_config = {
|
|
|
160
175
|
"route_dependencies": get_route_dependencies(),
|
|
161
176
|
}
|
|
162
177
|
|
|
178
|
+
# Add collections_get_request_model if collection search is enabled
|
|
179
|
+
if ENABLE_COLLECTIONS_SEARCH:
|
|
180
|
+
app_config["collections_get_request_model"] = collections_get_request_model
|
|
181
|
+
|
|
163
182
|
api = StacApi(**app_config)
|
|
164
183
|
|
|
165
184
|
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
5
|
from base64 import urlsafe_b64decode, urlsafe_b64encode
|
|
6
|
+
from collections.abc import Iterable
|
|
6
7
|
from copy import deepcopy
|
|
7
|
-
from typing import Any, Dict,
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple, Type
|
|
8
9
|
|
|
9
10
|
import attr
|
|
10
11
|
import orjson
|
|
@@ -16,7 +17,7 @@ from starlette.requests import Request
|
|
|
16
17
|
|
|
17
18
|
from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
|
|
18
19
|
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
|
|
19
|
-
from stac_fastapi.core.utilities import bbox2polygon, get_max_limit
|
|
20
|
+
from stac_fastapi.core.utilities import bbox2polygon, get_bool_env, get_max_limit
|
|
20
21
|
from stac_fastapi.extensions.core.transaction.request import (
|
|
21
22
|
PartialCollection,
|
|
22
23
|
PartialItem,
|
|
@@ -153,31 +154,95 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
153
154
|
"""CORE LOGIC"""
|
|
154
155
|
|
|
155
156
|
async def get_all_collections(
|
|
156
|
-
self,
|
|
157
|
+
self,
|
|
158
|
+
token: Optional[str],
|
|
159
|
+
limit: int,
|
|
160
|
+
request: Request,
|
|
161
|
+
sort: Optional[List[Dict[str, Any]]] = None,
|
|
162
|
+
q: Optional[List[str]] = None,
|
|
157
163
|
) -> Tuple[List[Dict[str, Any]], Optional[str]]:
|
|
158
|
-
"""
|
|
159
|
-
Retrieve a list of all collections from Opensearch, supporting pagination.
|
|
164
|
+
"""Retrieve a list of collections from Elasticsearch, supporting pagination.
|
|
160
165
|
|
|
161
166
|
Args:
|
|
162
167
|
token (Optional[str]): The pagination token.
|
|
163
168
|
limit (int): The number of results to return.
|
|
169
|
+
request (Request): The FastAPI request object.
|
|
170
|
+
sort (Optional[List[Dict[str, Any]]]): Optional sort parameter from the request.
|
|
171
|
+
q (Optional[List[str]]): Free text search terms.
|
|
164
172
|
|
|
165
173
|
Returns:
|
|
166
174
|
A tuple of (collections, next pagination token if any).
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
HTTPException: If sorting is requested on a field that is not sortable.
|
|
167
178
|
"""
|
|
168
|
-
|
|
169
|
-
|
|
179
|
+
# Define sortable fields based on the ES_COLLECTIONS_MAPPINGS
|
|
180
|
+
sortable_fields = ["id", "extent.temporal.interval", "temporal"]
|
|
181
|
+
|
|
182
|
+
# Format the sort parameter
|
|
183
|
+
formatted_sort = []
|
|
184
|
+
if sort:
|
|
185
|
+
for item in sort:
|
|
186
|
+
field = item.get("field")
|
|
187
|
+
direction = item.get("direction", "asc")
|
|
188
|
+
if field:
|
|
189
|
+
# Validate that the field is sortable
|
|
190
|
+
if field not in sortable_fields:
|
|
191
|
+
raise HTTPException(
|
|
192
|
+
status_code=400,
|
|
193
|
+
detail=f"Field '{field}' is not sortable. Sortable fields are: {', '.join(sortable_fields)}. "
|
|
194
|
+
+ "Text fields are not sortable by default in OpenSearch. "
|
|
195
|
+
+ "To make a field sortable, update the mapping to use 'keyword' type or add a '.keyword' subfield. ",
|
|
196
|
+
)
|
|
197
|
+
formatted_sort.append({field: {"order": direction}})
|
|
198
|
+
# Always include id as a secondary sort to ensure consistent pagination
|
|
199
|
+
if not any("id" in item for item in formatted_sort):
|
|
200
|
+
formatted_sort.append({"id": {"order": "asc"}})
|
|
201
|
+
else:
|
|
202
|
+
formatted_sort = [{"id": {"order": "asc"}}]
|
|
203
|
+
|
|
204
|
+
body = {
|
|
205
|
+
"sort": formatted_sort,
|
|
170
206
|
"size": limit,
|
|
171
207
|
}
|
|
172
208
|
|
|
173
|
-
# Only add search_after to the query if token is not None and not empty
|
|
174
209
|
if token:
|
|
175
|
-
search_after = [token]
|
|
176
|
-
|
|
210
|
+
body["search_after"] = [token]
|
|
211
|
+
|
|
212
|
+
# Apply free text query if provided
|
|
213
|
+
if q:
|
|
214
|
+
# For collections, we want to search across all relevant fields
|
|
215
|
+
should_clauses = []
|
|
216
|
+
|
|
217
|
+
# For each search term
|
|
218
|
+
for term in q:
|
|
219
|
+
# Create a multi_match query for each term
|
|
220
|
+
for field in [
|
|
221
|
+
"id",
|
|
222
|
+
"title",
|
|
223
|
+
"description",
|
|
224
|
+
"keywords",
|
|
225
|
+
"summaries.platform",
|
|
226
|
+
"summaries.constellation",
|
|
227
|
+
"providers.name",
|
|
228
|
+
"providers.url",
|
|
229
|
+
]:
|
|
230
|
+
should_clauses.append(
|
|
231
|
+
{
|
|
232
|
+
"wildcard": {
|
|
233
|
+
field: {"value": f"*{term}*", "case_insensitive": True}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Add the query to the body using bool query with should clauses
|
|
239
|
+
body["query"] = {
|
|
240
|
+
"bool": {"should": should_clauses, "minimum_should_match": 1}
|
|
241
|
+
}
|
|
177
242
|
|
|
178
243
|
response = await self.client.search(
|
|
179
244
|
index=COLLECTIONS_INDEX,
|
|
180
|
-
body=
|
|
245
|
+
body=body,
|
|
181
246
|
)
|
|
182
247
|
|
|
183
248
|
hits = response["hits"]["hits"]
|
|
@@ -301,21 +366,94 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
301
366
|
if not datetime_search:
|
|
302
367
|
return search, datetime_search
|
|
303
368
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
369
|
+
# USE_DATETIME env var
|
|
370
|
+
# True: Search by datetime, if null search by start/end datetime
|
|
371
|
+
# False: Always search only by start/end datetime
|
|
372
|
+
USE_DATETIME = get_bool_env("USE_DATETIME", default=True)
|
|
373
|
+
|
|
374
|
+
if USE_DATETIME:
|
|
375
|
+
if "eq" in datetime_search:
|
|
376
|
+
# For exact matches, include:
|
|
377
|
+
# 1. Items with matching exact datetime
|
|
378
|
+
# 2. Items with datetime:null where the time falls within their range
|
|
379
|
+
should = [
|
|
380
|
+
Q(
|
|
381
|
+
"bool",
|
|
382
|
+
filter=[
|
|
383
|
+
Q("exists", field="properties.datetime"),
|
|
384
|
+
Q(
|
|
385
|
+
"term",
|
|
386
|
+
**{"properties__datetime": datetime_search["eq"]},
|
|
387
|
+
),
|
|
388
|
+
],
|
|
389
|
+
),
|
|
390
|
+
Q(
|
|
391
|
+
"bool",
|
|
392
|
+
must_not=[Q("exists", field="properties.datetime")],
|
|
393
|
+
filter=[
|
|
394
|
+
Q("exists", field="properties.start_datetime"),
|
|
395
|
+
Q("exists", field="properties.end_datetime"),
|
|
396
|
+
Q(
|
|
397
|
+
"range",
|
|
398
|
+
properties__start_datetime={
|
|
399
|
+
"lte": datetime_search["eq"]
|
|
400
|
+
},
|
|
401
|
+
),
|
|
402
|
+
Q(
|
|
403
|
+
"range",
|
|
404
|
+
properties__end_datetime={"gte": datetime_search["eq"]},
|
|
405
|
+
),
|
|
406
|
+
],
|
|
407
|
+
),
|
|
408
|
+
]
|
|
409
|
+
else:
|
|
410
|
+
# For date ranges, include:
|
|
411
|
+
# 1. Items with datetime in the range
|
|
412
|
+
# 2. Items with datetime:null that overlap the search range
|
|
413
|
+
should = [
|
|
414
|
+
Q(
|
|
415
|
+
"bool",
|
|
416
|
+
filter=[
|
|
417
|
+
Q("exists", field="properties.datetime"),
|
|
418
|
+
Q(
|
|
419
|
+
"range",
|
|
420
|
+
properties__datetime={
|
|
421
|
+
"gte": datetime_search["gte"],
|
|
422
|
+
"lte": datetime_search["lte"],
|
|
423
|
+
},
|
|
424
|
+
),
|
|
425
|
+
],
|
|
426
|
+
),
|
|
427
|
+
Q(
|
|
428
|
+
"bool",
|
|
429
|
+
must_not=[Q("exists", field="properties.datetime")],
|
|
430
|
+
filter=[
|
|
431
|
+
Q("exists", field="properties.start_datetime"),
|
|
432
|
+
Q("exists", field="properties.end_datetime"),
|
|
433
|
+
Q(
|
|
434
|
+
"range",
|
|
435
|
+
properties__start_datetime={
|
|
436
|
+
"lte": datetime_search["lte"]
|
|
437
|
+
},
|
|
438
|
+
),
|
|
439
|
+
Q(
|
|
440
|
+
"range",
|
|
441
|
+
properties__end_datetime={
|
|
442
|
+
"gte": datetime_search["gte"]
|
|
443
|
+
},
|
|
444
|
+
),
|
|
445
|
+
],
|
|
446
|
+
),
|
|
447
|
+
]
|
|
448
|
+
|
|
449
|
+
return (
|
|
450
|
+
search.query(Q("bool", should=should, minimum_should_match=1)),
|
|
451
|
+
datetime_search,
|
|
452
|
+
)
|
|
453
|
+
else:
|
|
454
|
+
if "eq" in datetime_search:
|
|
455
|
+
filter_query = Q(
|
|
317
456
|
"bool",
|
|
318
|
-
must_not=[Q("exists", field="properties.datetime")],
|
|
319
457
|
filter=[
|
|
320
458
|
Q("exists", field="properties.start_datetime"),
|
|
321
459
|
Q("exists", field="properties.end_datetime"),
|
|
@@ -328,29 +466,10 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
328
466
|
properties__end_datetime={"gte": datetime_search["eq"]},
|
|
329
467
|
),
|
|
330
468
|
],
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
# For date ranges, include:
|
|
335
|
-
# 1. Items with datetime in the range
|
|
336
|
-
# 2. Items with datetime:null that overlap the search range
|
|
337
|
-
should = [
|
|
338
|
-
Q(
|
|
339
|
-
"bool",
|
|
340
|
-
filter=[
|
|
341
|
-
Q("exists", field="properties.datetime"),
|
|
342
|
-
Q(
|
|
343
|
-
"range",
|
|
344
|
-
properties__datetime={
|
|
345
|
-
"gte": datetime_search["gte"],
|
|
346
|
-
"lte": datetime_search["lte"],
|
|
347
|
-
},
|
|
348
|
-
),
|
|
349
|
-
],
|
|
350
|
-
),
|
|
351
|
-
Q(
|
|
469
|
+
)
|
|
470
|
+
else:
|
|
471
|
+
filter_query = Q(
|
|
352
472
|
"bool",
|
|
353
|
-
must_not=[Q("exists", field="properties.datetime")],
|
|
354
473
|
filter=[
|
|
355
474
|
Q("exists", field="properties.start_datetime"),
|
|
356
475
|
Q("exists", field="properties.end_datetime"),
|
|
@@ -363,13 +482,8 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
363
482
|
properties__end_datetime={"gte": datetime_search["gte"]},
|
|
364
483
|
),
|
|
365
484
|
],
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
return (
|
|
370
|
-
search.query(Q("bool", should=should, minimum_should_match=1)),
|
|
371
|
-
datetime_search,
|
|
372
|
-
)
|
|
485
|
+
)
|
|
486
|
+
return search.query(filter_query), datetime_search
|
|
373
487
|
|
|
374
488
|
@staticmethod
|
|
375
489
|
def apply_bbox_filter(search: Search, bbox: List):
|
|
@@ -679,14 +793,21 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
679
793
|
|
|
680
794
|
"""
|
|
681
795
|
await self.check_collection_exists(collection_id=item["collection"])
|
|
796
|
+
alias = index_alias_by_collection_id(item["collection"])
|
|
797
|
+
doc_id = mk_item_id(item["id"], item["collection"])
|
|
682
798
|
|
|
683
|
-
if not exist_ok
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
799
|
+
if not exist_ok:
|
|
800
|
+
alias_exists = await self.client.indices.exists_alias(name=alias)
|
|
801
|
+
|
|
802
|
+
if alias_exists:
|
|
803
|
+
alias_info = await self.client.indices.get_alias(name=alias)
|
|
804
|
+
indices = list(alias_info.keys())
|
|
805
|
+
|
|
806
|
+
for index in indices:
|
|
807
|
+
if await self.client.exists(index=index, id=doc_id):
|
|
808
|
+
raise ConflictError(
|
|
809
|
+
f"Item {item['id']} in collection {item['collection']} already exists"
|
|
810
|
+
)
|
|
690
811
|
|
|
691
812
|
return self.item_serializer.stac_to_db(item, base_url)
|
|
692
813
|
|
|
@@ -903,7 +1024,6 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
903
1024
|
"add",
|
|
904
1025
|
"replace",
|
|
905
1026
|
]:
|
|
906
|
-
|
|
907
1027
|
if operation.path == "collection" and collection_id != operation.value:
|
|
908
1028
|
await self.check_collection_exists(collection_id=operation.value)
|
|
909
1029
|
new_collection_id = operation.value
|
|
@@ -957,8 +1077,8 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
957
1077
|
"script": {
|
|
958
1078
|
"lang": "painless",
|
|
959
1079
|
"source": (
|
|
960
|
-
f"""ctx._id = ctx._id.replace('{collection_id}', '{new_collection_id}');"""
|
|
961
|
-
f"""ctx._source.collection = '{new_collection_id}';"""
|
|
1080
|
+
f"""ctx._id = ctx._id.replace('{collection_id}', '{new_collection_id}');"""
|
|
1081
|
+
f"""ctx._source.collection = '{new_collection_id}';"""
|
|
962
1082
|
),
|
|
963
1083
|
},
|
|
964
1084
|
},
|
|
@@ -1180,7 +1300,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1180
1300
|
"source": {"index": f"{ITEMS_INDEX_PREFIX}{collection_id}"},
|
|
1181
1301
|
"script": {
|
|
1182
1302
|
"lang": "painless",
|
|
1183
|
-
"source": f"""ctx._id = ctx._id.replace('{collection_id}', '{collection["id"]}'); ctx._source.collection = '{collection["id"]}' ;""",
|
|
1303
|
+
"source": f"""ctx._id = ctx._id.replace('{collection_id}', '{collection["id"]}'); ctx._source.collection = '{collection["id"]}' ;""",
|
|
1184
1304
|
},
|
|
1185
1305
|
},
|
|
1186
1306
|
wait_for_completion=True,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.4.0"
|
{stac_fastapi_opensearch-6.3.0.dist-info → stac_fastapi_opensearch-6.4.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: stac-fastapi-opensearch
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.4.0
|
|
4
4
|
Summary: Opensearch stac-fastapi backend.
|
|
5
5
|
Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
|
|
6
6
|
License: MIT
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: stac-fastapi-core==6.
|
|
19
|
-
Requires-Dist: sfeos-helpers==6.
|
|
18
|
+
Requires-Dist: stac-fastapi-core==6.4.0
|
|
19
|
+
Requires-Dist: sfeos-helpers==6.4.0
|
|
20
20
|
Requires-Dist: opensearch-py~=2.8.0
|
|
21
21
|
Requires-Dist: opensearch-py[async]~=2.8.0
|
|
22
22
|
Requires-Dist: uvicorn~=0.23.0
|
|
@@ -73,11 +73,10 @@ SFEOS (stac-fastapi-elasticsearch-opensearch) is a high-performance, scalable AP
|
|
|
73
73
|
- **Scale to millions of geospatial assets** with fast search performance through optimized spatial indexing and query capabilities
|
|
74
74
|
- **Support OGC-compliant filtering** including spatial operations (intersects, contains, etc.) and temporal queries
|
|
75
75
|
- **Perform geospatial aggregations** to analyze data distribution across space and time
|
|
76
|
+
- **Enhanced collection search capabilities** with support for sorting and field selection
|
|
76
77
|
|
|
77
78
|
This implementation builds on the STAC-FastAPI framework, providing a production-ready solution specifically optimized for Elasticsearch and OpenSearch databases. It's ideal for organizations managing large geospatial data catalogs who need efficient discovery and access capabilities through standardized APIs.
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
80
|
## Common Deployment Patterns
|
|
82
81
|
|
|
83
82
|
stac-fastapi-elasticsearch-opensearch can be deployed in several ways depending on your needs:
|
|
@@ -109,6 +108,7 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
|
|
|
109
108
|
- [Common Deployment Patterns](#common-deployment-patterns)
|
|
110
109
|
- [Technologies](#technologies)
|
|
111
110
|
- [Table of Contents](#table-of-contents)
|
|
111
|
+
- [Collection Search Extensions](#collection-search-extensions)
|
|
112
112
|
- [Documentation \& Resources](#documentation--resources)
|
|
113
113
|
- [Package Structure](#package-structure)
|
|
114
114
|
- [Examples](#examples)
|
|
@@ -150,6 +150,37 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
|
|
|
150
150
|
- [Gitter Chat](https://app.gitter.im/#/room/#stac-fastapi-elasticsearch_community:gitter.im) - For real-time discussions
|
|
151
151
|
- [GitHub Discussions](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/discussions) - For longer-form questions and answers
|
|
152
152
|
|
|
153
|
+
## Collection Search Extensions
|
|
154
|
+
|
|
155
|
+
SFEOS implements extended capabilities for the `/collections` endpoint, allowing for more powerful collection discovery:
|
|
156
|
+
|
|
157
|
+
- **Sorting**: Sort collections by sortable fields using the `sortby` parameter
|
|
158
|
+
- Example: `/collections?sortby=+id` (ascending sort by ID)
|
|
159
|
+
- Example: `/collections?sortby=-id` (descending sort by ID)
|
|
160
|
+
- Example: `/collections?sortby=-temporal` (descending sort by temporal extent)
|
|
161
|
+
|
|
162
|
+
- **Field Selection**: Request only specific fields to be returned using the `fields` parameter
|
|
163
|
+
- Example: `/collections?fields=id,title,description`
|
|
164
|
+
- This helps reduce payload size when only certain fields are needed
|
|
165
|
+
|
|
166
|
+
- **Free Text Search**: Search across collection text fields using the `q` parameter
|
|
167
|
+
- Example: `/collections?q=landsat`
|
|
168
|
+
- Searches across multiple text fields including title, description, and keywords
|
|
169
|
+
- Supports partial word matching and relevance-based sorting
|
|
170
|
+
|
|
171
|
+
These extensions make it easier to build user interfaces that display and navigate through collections efficiently.
|
|
172
|
+
|
|
173
|
+
> **Configuration**: Collection search extensions can be disabled by setting the `ENABLE_COLLECTIONS_SEARCH` environment variable to `false`. By default, these extensions are enabled.
|
|
174
|
+
|
|
175
|
+
> **Note**: Sorting is only available on fields that are indexed for sorting in Elasticsearch/OpenSearch. With the default mappings, you can sort on:
|
|
176
|
+
> - `id` (keyword field)
|
|
177
|
+
> - `extent.temporal.interval` (date field)
|
|
178
|
+
> - `temporal` (alias to extent.temporal.interval)
|
|
179
|
+
>
|
|
180
|
+
> Text fields like `title` and `description` are not sortable by default as they use text analysis for better search capabilities. Attempting to sort on these fields will result in a user-friendly error message explaining which fields are sortable and how to make additional fields sortable by updating the mappings.
|
|
181
|
+
>
|
|
182
|
+
> **Important**: Adding keyword fields to make text fields sortable can significantly increase the index size, especially for large text fields. Consider the storage implications when deciding which fields to make sortable.
|
|
183
|
+
|
|
153
184
|
## Package Structure
|
|
154
185
|
|
|
155
186
|
This project is organized into several packages, each with a specific purpose:
|
|
@@ -280,10 +311,12 @@ You can customize additional settings in your `.env` file:
|
|
|
280
311
|
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
|
|
281
312
|
| `RAISE_ON_BULK_ERROR` | Controls whether bulk insert operations raise exceptions on errors. If set to `true`, the operation will stop and raise an exception when an error occurs. If set to `false`, errors will be logged, and the operation will continue. **Note:** STAC Item and ItemCollection validation errors will always raise, regardless of this flag. | `false` | Optional |
|
|
282
313
|
| `DATABASE_REFRESH` | Controls whether database operations refresh the index immediately after changes. If set to `true`, changes will be immediately searchable. If set to `false`, changes may not be immediately visible but can improve performance for bulk operations. If set to `wait_for`, changes will wait for the next refresh cycle to become visible. | `false` | Optional |
|
|
314
|
+
| `ENABLE_COLLECTIONS_SEARCH` | Enable collection search extensions (sort, fields). | `true` | Optional |
|
|
283
315
|
| `ENABLE_TRANSACTIONS_EXTENSIONS` | Enables or disables the Transactions and Bulk Transactions API extensions. If set to `false`, the POST `/collections` route and related transaction endpoints (including bulk transaction operations) will be unavailable in the API. This is useful for deployments where mutating the catalog via the API should be prevented. | `true` | Optional |
|
|
284
316
|
| `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
|
|
285
317
|
| `STAC_INDEX_ASSETS` | Controls if Assets are indexed when added to Elasticsearch/Opensearch. This allows asset fields to be included in search queries. | `false` | Optional |
|
|
286
318
|
| `ENV_MAX_LIMIT` | Configures the environment variable in SFEOS to override the default `MAX_LIMIT`, which controls the limit parameter for returned items and STAC collections. | `10,000` | Optional |
|
|
319
|
+
| `USE_DATETIME` | Configures the datetime search behavior in SFEOS. When enabled, searches both datetime field and falls back to start_datetime/end_datetime range for items with null datetime. When disabled, searches only by start_datetime/end_datetime range. | True | Optional |
|
|
287
320
|
|
|
288
321
|
> [!NOTE]
|
|
289
322
|
> The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, `ES_VERIFY_CERTS` and `ES_TIMEOUT` apply to both Elasticsearch and OpenSearch backends, so there is no need to rename the key names to `OS_` even if you're using OpenSearch.
|
|
@@ -425,6 +458,10 @@ The system uses a precise naming convention:
|
|
|
425
458
|
- **Root Path Configuration**: The application root path is the base URL by default.
|
|
426
459
|
- For AWS Lambda with Gateway API: Set `STAC_FASTAPI_ROOT_PATH` to match the Gateway API stage name (e.g., `/v1`)
|
|
427
460
|
|
|
461
|
+
- **Feature Configuration**: Control which features are enabled:
|
|
462
|
+
- `ENABLE_COLLECTIONS_SEARCH`: Set to `true` (default) to enable collection search extensions (sort, fields). Set to `false` to disable.
|
|
463
|
+
- `ENABLE_TRANSACTIONS_EXTENSIONS`: Set to `true` (default) to enable transaction extensions. Set to `false` to disable.
|
|
464
|
+
|
|
428
465
|
|
|
429
466
|
## Collection Pagination
|
|
430
467
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
stac_fastapi/opensearch/__init__.py,sha256=iJWMUgn7mUvmuPQSO_FlyhJ5eDdbbfmGv1qnFOX5-qk,28
|
|
2
|
+
stac_fastapi/opensearch/app.py,sha256=fd9S10gFUHG5GkuA8c83wYEOet-USNKu-B-p26l6glY,7485
|
|
3
|
+
stac_fastapi/opensearch/config.py,sha256=zGx4-4c5zEnu_Bh8Ra3SkIC83tluDiz-wKYQclRRDJA,5179
|
|
4
|
+
stac_fastapi/opensearch/database_logic.py,sha256=l7f__kvTUJlvoIGPxdYdhXkT3dZ_YwPj-kNCY1FC-Os,61259
|
|
5
|
+
stac_fastapi/opensearch/version.py,sha256=1xfRb7s1Stv055oEewNJpZn9OQUUcaJvgSzCsYLHYSQ,45
|
|
6
|
+
stac_fastapi_opensearch-6.4.0.dist-info/METADATA,sha256=lwQchY6F_5gNl9eWmwPRmnSJYQ0OWFHgcX1Hg5oboDM,39650
|
|
7
|
+
stac_fastapi_opensearch-6.4.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
8
|
+
stac_fastapi_opensearch-6.4.0.dist-info/entry_points.txt,sha256=zjZ0Xsr9BUNJqMkdPpl6zEIUykv1uFdJtNELFRChp0w,76
|
|
9
|
+
stac_fastapi_opensearch-6.4.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
|
|
10
|
+
stac_fastapi_opensearch-6.4.0.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
stac_fastapi/opensearch/__init__.py,sha256=iJWMUgn7mUvmuPQSO_FlyhJ5eDdbbfmGv1qnFOX5-qk,28
|
|
2
|
-
stac_fastapi/opensearch/app.py,sha256=KTXD2jdAlcrrXr1o0bRMFBJp-B3fo0F3gyY2Pcc9rMA,6558
|
|
3
|
-
stac_fastapi/opensearch/config.py,sha256=zGx4-4c5zEnu_Bh8Ra3SkIC83tluDiz-wKYQclRRDJA,5179
|
|
4
|
-
stac_fastapi/opensearch/database_logic.py,sha256=jvFsyQYmFTYkakVg6kcQt1zx-IkqmCC2093gtgNwuPQ,56024
|
|
5
|
-
stac_fastapi/opensearch/version.py,sha256=rBLPQyvMDNA0PA0jXfByTouJPJn5p0wXiqmUWJMIfYc,45
|
|
6
|
-
stac_fastapi_opensearch-6.3.0.dist-info/METADATA,sha256=B-xB5wIjNHXI9bkk5Oeg6wuK69j8nE-p529wXq8oIcw,36590
|
|
7
|
-
stac_fastapi_opensearch-6.3.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
8
|
-
stac_fastapi_opensearch-6.3.0.dist-info/entry_points.txt,sha256=zjZ0Xsr9BUNJqMkdPpl6zEIUykv1uFdJtNELFRChp0w,76
|
|
9
|
-
stac_fastapi_opensearch-6.3.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
|
|
10
|
-
stac_fastapi_opensearch-6.3.0.dist-info/RECORD,,
|
|
File without changes
|
{stac_fastapi_opensearch-6.3.0.dist-info → stac_fastapi_opensearch-6.4.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{stac_fastapi_opensearch-6.3.0.dist-info → stac_fastapi_opensearch-6.4.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|