stac-fastapi-elasticsearch 6.4.0__py3-none-any.whl → 6.5.1__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/elasticsearch/app.py +74 -9
- stac_fastapi/elasticsearch/database_logic.py +170 -16
- stac_fastapi/elasticsearch/version.py +1 -1
- {stac_fastapi_elasticsearch-6.4.0.dist-info → stac_fastapi_elasticsearch-6.5.1.dist-info}/METADATA +34 -12
- stac_fastapi_elasticsearch-6.5.1.dist-info/RECORD +10 -0
- stac_fastapi_elasticsearch-6.4.0.dist-info/RECORD +0 -10
- {stac_fastapi_elasticsearch-6.4.0.dist-info → stac_fastapi_elasticsearch-6.5.1.dist-info}/WHEEL +0 -0
- {stac_fastapi_elasticsearch-6.4.0.dist-info → stac_fastapi_elasticsearch-6.5.1.dist-info}/entry_points.txt +0 -0
- {stac_fastapi_elasticsearch-6.4.0.dist-info → stac_fastapi_elasticsearch-6.5.1.dist-info}/top_level.txt +0 -0
|
@@ -23,6 +23,9 @@ from stac_fastapi.core.extensions.aggregation import (
|
|
|
23
23
|
EsAggregationExtensionGetRequest,
|
|
24
24
|
EsAggregationExtensionPostRequest,
|
|
25
25
|
)
|
|
26
|
+
from stac_fastapi.core.extensions.collections_search import (
|
|
27
|
+
CollectionsSearchEndpointExtension,
|
|
28
|
+
)
|
|
26
29
|
from stac_fastapi.core.extensions.fields import FieldsExtension
|
|
27
30
|
from stac_fastapi.core.rate_limit import setup_rate_limit
|
|
28
31
|
from stac_fastapi.core.route_dependencies import get_route_dependencies
|
|
@@ -34,9 +37,11 @@ from stac_fastapi.elasticsearch.database_logic import (
|
|
|
34
37
|
create_collection_index,
|
|
35
38
|
create_index_templates,
|
|
36
39
|
)
|
|
37
|
-
from stac_fastapi.extensions.core import (
|
|
40
|
+
from stac_fastapi.extensions.core import (
|
|
38
41
|
AggregationExtension,
|
|
39
42
|
CollectionSearchExtension,
|
|
43
|
+
CollectionSearchFilterExtension,
|
|
44
|
+
CollectionSearchPostExtension,
|
|
40
45
|
FilterExtension,
|
|
41
46
|
FreeTextExtension,
|
|
42
47
|
SortExtension,
|
|
@@ -57,8 +62,14 @@ logger = logging.getLogger(__name__)
|
|
|
57
62
|
|
|
58
63
|
TRANSACTIONS_EXTENSIONS = get_bool_env("ENABLE_TRANSACTIONS_EXTENSIONS", default=True)
|
|
59
64
|
ENABLE_COLLECTIONS_SEARCH = get_bool_env("ENABLE_COLLECTIONS_SEARCH", default=True)
|
|
65
|
+
ENABLE_COLLECTIONS_SEARCH_ROUTE = get_bool_env(
|
|
66
|
+
"ENABLE_COLLECTIONS_SEARCH_ROUTE", default=False
|
|
67
|
+
)
|
|
60
68
|
logger.info("TRANSACTIONS_EXTENSIONS is set to %s", TRANSACTIONS_EXTENSIONS)
|
|
61
69
|
logger.info("ENABLE_COLLECTIONS_SEARCH is set to %s", ENABLE_COLLECTIONS_SEARCH)
|
|
70
|
+
logger.info(
|
|
71
|
+
"ENABLE_COLLECTIONS_SEARCH_ROUTE is set to %s", ENABLE_COLLECTIONS_SEARCH_ROUTE
|
|
72
|
+
)
|
|
62
73
|
|
|
63
74
|
settings = ElasticsearchSettings()
|
|
64
75
|
session = Session.create_from_settings(settings)
|
|
@@ -116,16 +127,18 @@ if TRANSACTIONS_EXTENSIONS:
|
|
|
116
127
|
|
|
117
128
|
extensions = [aggregation_extension] + search_extensions
|
|
118
129
|
|
|
119
|
-
#
|
|
120
|
-
|
|
130
|
+
# Collection search related variables
|
|
131
|
+
collections_get_request_model = None
|
|
132
|
+
|
|
133
|
+
if ENABLE_COLLECTIONS_SEARCH or ENABLE_COLLECTIONS_SEARCH_ROUTE:
|
|
121
134
|
# Create collection search extensions
|
|
122
135
|
collection_search_extensions = [
|
|
123
|
-
|
|
136
|
+
QueryExtension(conformance_classes=[QueryConformanceClasses.COLLECTIONS]),
|
|
124
137
|
SortExtension(conformance_classes=[SortConformanceClasses.COLLECTIONS]),
|
|
125
138
|
FieldsExtension(conformance_classes=[FieldsConformanceClasses.COLLECTIONS]),
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
CollectionSearchFilterExtension(
|
|
140
|
+
conformance_classes=[FilterConformanceClasses.COLLECTIONS]
|
|
141
|
+
),
|
|
129
142
|
FreeTextExtension(conformance_classes=[FreeTextConformanceClasses.COLLECTIONS]),
|
|
130
143
|
]
|
|
131
144
|
|
|
@@ -135,7 +148,58 @@ if ENABLE_COLLECTIONS_SEARCH:
|
|
|
135
148
|
)
|
|
136
149
|
collections_get_request_model = collection_search_ext.GET
|
|
137
150
|
|
|
151
|
+
# Create a post request model for collection search
|
|
152
|
+
collection_search_post_request_model = create_post_request_model(
|
|
153
|
+
collection_search_extensions
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Create collection search extensions if enabled
|
|
157
|
+
if ENABLE_COLLECTIONS_SEARCH:
|
|
158
|
+
# Initialize collection search POST extension
|
|
159
|
+
collection_search_post_ext = CollectionSearchPostExtension(
|
|
160
|
+
client=CoreClient(
|
|
161
|
+
database=database_logic,
|
|
162
|
+
session=session,
|
|
163
|
+
post_request_model=collection_search_post_request_model,
|
|
164
|
+
landing_page_id=os.getenv("STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi"),
|
|
165
|
+
),
|
|
166
|
+
settings=settings,
|
|
167
|
+
POST=collection_search_post_request_model,
|
|
168
|
+
conformance_classes=[
|
|
169
|
+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search",
|
|
170
|
+
QueryConformanceClasses.COLLECTIONS,
|
|
171
|
+
FilterConformanceClasses.COLLECTIONS,
|
|
172
|
+
FreeTextConformanceClasses.COLLECTIONS,
|
|
173
|
+
SortConformanceClasses.COLLECTIONS,
|
|
174
|
+
FieldsConformanceClasses.COLLECTIONS,
|
|
175
|
+
],
|
|
176
|
+
)
|
|
138
177
|
extensions.append(collection_search_ext)
|
|
178
|
+
extensions.append(collection_search_post_ext)
|
|
179
|
+
|
|
180
|
+
if ENABLE_COLLECTIONS_SEARCH_ROUTE:
|
|
181
|
+
# Initialize collections-search endpoint extension
|
|
182
|
+
collections_search_endpoint_ext = CollectionsSearchEndpointExtension(
|
|
183
|
+
client=CoreClient(
|
|
184
|
+
database=database_logic,
|
|
185
|
+
session=session,
|
|
186
|
+
post_request_model=collection_search_post_request_model,
|
|
187
|
+
landing_page_id=os.getenv("STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi"),
|
|
188
|
+
),
|
|
189
|
+
settings=settings,
|
|
190
|
+
GET=collections_get_request_model,
|
|
191
|
+
POST=collection_search_post_request_model,
|
|
192
|
+
conformance_classes=[
|
|
193
|
+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search",
|
|
194
|
+
QueryConformanceClasses.COLLECTIONS,
|
|
195
|
+
FilterConformanceClasses.COLLECTIONS,
|
|
196
|
+
FreeTextConformanceClasses.COLLECTIONS,
|
|
197
|
+
SortConformanceClasses.COLLECTIONS,
|
|
198
|
+
FieldsConformanceClasses.COLLECTIONS,
|
|
199
|
+
],
|
|
200
|
+
)
|
|
201
|
+
extensions.append(collections_search_endpoint_ext)
|
|
202
|
+
|
|
139
203
|
|
|
140
204
|
database_logic.extensions = [type(ext).__name__ for ext in extensions]
|
|
141
205
|
|
|
@@ -175,8 +239,8 @@ app_config = {
|
|
|
175
239
|
"route_dependencies": get_route_dependencies(),
|
|
176
240
|
}
|
|
177
241
|
|
|
178
|
-
# Add collections_get_request_model if
|
|
179
|
-
if
|
|
242
|
+
# Add collections_get_request_model if it was created
|
|
243
|
+
if collections_get_request_model:
|
|
180
244
|
app_config["collections_get_request_model"] = collections_get_request_model
|
|
181
245
|
|
|
182
246
|
api = StacApi(**app_config)
|
|
@@ -193,6 +257,7 @@ async def lifespan(app: FastAPI):
|
|
|
193
257
|
app = api.app
|
|
194
258
|
app.router.lifespan_context = lifespan
|
|
195
259
|
app.root_path = os.getenv("STAC_FASTAPI_ROOT_PATH", "")
|
|
260
|
+
|
|
196
261
|
# Add rate limit
|
|
197
262
|
setup_rate_limit(app, rate_limit=os.getenv("STAC_FASTAPI_RATE_LIMIT"))
|
|
198
263
|
|
|
@@ -176,7 +176,10 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
176
176
|
request: Request,
|
|
177
177
|
sort: Optional[List[Dict[str, Any]]] = None,
|
|
178
178
|
q: Optional[List[str]] = None,
|
|
179
|
-
|
|
179
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
180
|
+
query: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
181
|
+
datetime: Optional[str] = None,
|
|
182
|
+
) -> Tuple[List[Dict[str, Any]], Optional[str], Optional[int]]:
|
|
180
183
|
"""Retrieve a list of collections from Elasticsearch, supporting pagination.
|
|
181
184
|
|
|
182
185
|
Args:
|
|
@@ -185,6 +188,9 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
185
188
|
request (Request): The FastAPI request object.
|
|
186
189
|
sort (Optional[List[Dict[str, Any]]]): Optional sort parameter from the request.
|
|
187
190
|
q (Optional[List[str]]): Free text search terms.
|
|
191
|
+
query (Optional[Dict[str, Dict[str, Any]]]): Query extension parameters.
|
|
192
|
+
filter (Optional[Dict[str, Any]]): Structured query in CQL2 format.
|
|
193
|
+
datetime (Optional[str]): Temporal filter.
|
|
188
194
|
|
|
189
195
|
Returns:
|
|
190
196
|
A tuple of (collections, next pagination token if any).
|
|
@@ -222,8 +228,24 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
222
228
|
"size": limit,
|
|
223
229
|
}
|
|
224
230
|
|
|
231
|
+
# Handle search_after token - split by '|' to get all sort values
|
|
232
|
+
search_after = None
|
|
225
233
|
if token:
|
|
226
|
-
|
|
234
|
+
try:
|
|
235
|
+
# The token should be a pipe-separated string of sort values
|
|
236
|
+
# e.g., "2023-01-01T00:00:00Z|collection-1"
|
|
237
|
+
search_after = token.split("|")
|
|
238
|
+
# If the number of sort fields doesn't match token parts, ignore the token
|
|
239
|
+
if len(search_after) != len(formatted_sort):
|
|
240
|
+
search_after = None
|
|
241
|
+
except Exception:
|
|
242
|
+
search_after = None
|
|
243
|
+
|
|
244
|
+
if search_after is not None:
|
|
245
|
+
body["search_after"] = search_after
|
|
246
|
+
|
|
247
|
+
# Build the query part of the body
|
|
248
|
+
query_parts = []
|
|
227
249
|
|
|
228
250
|
# Apply free text query if provided
|
|
229
251
|
if q:
|
|
@@ -251,17 +273,86 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
251
273
|
}
|
|
252
274
|
)
|
|
253
275
|
|
|
254
|
-
# Add the query to the
|
|
255
|
-
|
|
256
|
-
"bool": {"should": should_clauses, "minimum_should_match": 1}
|
|
257
|
-
|
|
276
|
+
# Add the free text query to the query parts
|
|
277
|
+
query_parts.append(
|
|
278
|
+
{"bool": {"should": should_clauses, "minimum_should_match": 1}}
|
|
279
|
+
)
|
|
258
280
|
|
|
259
|
-
#
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
281
|
+
# Apply structured filter if provided
|
|
282
|
+
if filter:
|
|
283
|
+
# Convert string filter to dict if needed
|
|
284
|
+
if isinstance(filter, str):
|
|
285
|
+
filter = orjson.loads(filter)
|
|
286
|
+
# Convert the filter to an Elasticsearch query using the filter module
|
|
287
|
+
es_query = filter_module.to_es(await self.get_queryables_mapping(), filter)
|
|
288
|
+
query_parts.append(es_query)
|
|
289
|
+
|
|
290
|
+
# Apply query extension if provided
|
|
291
|
+
if query:
|
|
292
|
+
try:
|
|
293
|
+
# First create a search object to apply filters
|
|
294
|
+
search = Search(index=COLLECTIONS_INDEX)
|
|
295
|
+
|
|
296
|
+
# Process each field and operator in the query
|
|
297
|
+
for field_name, expr in query.items():
|
|
298
|
+
for op, value in expr.items():
|
|
299
|
+
# For collections, we don't need to prefix with 'properties__'
|
|
300
|
+
field = field_name
|
|
301
|
+
# Apply the filter using apply_stacql_filter
|
|
302
|
+
search = self.apply_stacql_filter(
|
|
303
|
+
search=search, op=op, field=field, value=value
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Convert the search object to a query dict and add it to query_parts
|
|
307
|
+
search_dict = search.to_dict()
|
|
308
|
+
if "query" in search_dict:
|
|
309
|
+
query_parts.append(search_dict["query"])
|
|
310
|
+
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.error(f"Error converting query to Elasticsearch: {e}")
|
|
313
|
+
# If there's an error, add a query that matches nothing
|
|
314
|
+
query_parts.append({"bool": {"must_not": {"match_all": {}}}})
|
|
315
|
+
raise
|
|
316
|
+
|
|
317
|
+
# Combine all query parts with AND logic if there are multiple
|
|
318
|
+
datetime_filter = None
|
|
319
|
+
if datetime:
|
|
320
|
+
datetime_filter = self._apply_collection_datetime_filter(datetime)
|
|
321
|
+
if datetime_filter:
|
|
322
|
+
query_parts.append(datetime_filter)
|
|
323
|
+
|
|
324
|
+
# Combine all query parts with AND logic
|
|
325
|
+
if query_parts:
|
|
326
|
+
body["query"] = (
|
|
327
|
+
query_parts[0]
|
|
328
|
+
if len(query_parts) == 1
|
|
329
|
+
else {"bool": {"must": query_parts}}
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Create a copy of the body for count query (without pagination and sorting)
|
|
333
|
+
count_body = body.copy()
|
|
334
|
+
if "search_after" in count_body:
|
|
335
|
+
del count_body["search_after"]
|
|
336
|
+
count_body["size"] = 0
|
|
337
|
+
|
|
338
|
+
# Create async tasks for both search and count
|
|
339
|
+
search_task = asyncio.create_task(
|
|
340
|
+
self.client.search(
|
|
341
|
+
index=COLLECTIONS_INDEX,
|
|
342
|
+
body=body,
|
|
343
|
+
)
|
|
263
344
|
)
|
|
264
345
|
|
|
346
|
+
count_task = asyncio.create_task(
|
|
347
|
+
self.client.count(
|
|
348
|
+
index=COLLECTIONS_INDEX,
|
|
349
|
+
body={"query": body.get("query", {"match_all": {}})},
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Wait for search task to complete
|
|
354
|
+
response = await search_task
|
|
355
|
+
|
|
265
356
|
hits = response["hits"]["hits"]
|
|
266
357
|
collections = [
|
|
267
358
|
self.collection_serializer.db_to_stac(
|
|
@@ -274,9 +365,59 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
274
365
|
if len(hits) == limit:
|
|
275
366
|
next_token_values = hits[-1].get("sort")
|
|
276
367
|
if next_token_values:
|
|
277
|
-
|
|
368
|
+
# Join all sort values with '|' to create the token
|
|
369
|
+
next_token = "|".join(str(val) for val in next_token_values)
|
|
278
370
|
|
|
279
|
-
|
|
371
|
+
# Get the total count of collections
|
|
372
|
+
matched = (
|
|
373
|
+
response["hits"]["total"]["value"]
|
|
374
|
+
if response["hits"]["total"]["relation"] == "eq"
|
|
375
|
+
else None
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# If count task is done, use its result
|
|
379
|
+
if count_task.done():
|
|
380
|
+
try:
|
|
381
|
+
matched = count_task.result().get("count")
|
|
382
|
+
except Exception as e:
|
|
383
|
+
logger.error(f"Count task failed: {e}")
|
|
384
|
+
|
|
385
|
+
return collections, next_token, matched
|
|
386
|
+
|
|
387
|
+
@staticmethod
|
|
388
|
+
def _apply_collection_datetime_filter(
|
|
389
|
+
datetime_str: Optional[str],
|
|
390
|
+
) -> Optional[Dict[str, Any]]:
|
|
391
|
+
"""Create a temporal filter for collections based on their extent."""
|
|
392
|
+
if not datetime_str:
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
# Parse the datetime string into start and end
|
|
396
|
+
if "/" in datetime_str:
|
|
397
|
+
start, end = datetime_str.split("/")
|
|
398
|
+
# Replace open-ended ranges with concrete dates
|
|
399
|
+
if start == "..":
|
|
400
|
+
# For open-ended start, use a very early date
|
|
401
|
+
start = "1800-01-01T00:00:00Z"
|
|
402
|
+
if end == "..":
|
|
403
|
+
# For open-ended end, use a far future date
|
|
404
|
+
end = "2999-12-31T23:59:59Z"
|
|
405
|
+
else:
|
|
406
|
+
# If it's just a single date, use it for both start and end
|
|
407
|
+
start = end = datetime_str
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
"bool": {
|
|
411
|
+
"must": [
|
|
412
|
+
# Check if any date in the array is less than or equal to the query end date
|
|
413
|
+
# This will match if the collection's start date is before or equal to the query end date
|
|
414
|
+
{"range": {"extent.temporal.interval": {"lte": end}}},
|
|
415
|
+
# Check if any date in the array is greater than or equal to the query start date
|
|
416
|
+
# This will match if the collection's end date is after or equal to the query start date
|
|
417
|
+
{"range": {"extent.temporal.interval": {"gte": start}}},
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
}
|
|
280
421
|
|
|
281
422
|
async def get_one_item(self, collection_id: str, item_id: str) -> Dict:
|
|
282
423
|
"""Retrieve a single item from the database.
|
|
@@ -540,18 +681,31 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
540
681
|
|
|
541
682
|
Args:
|
|
542
683
|
search (Search): The search object to apply the filter to.
|
|
543
|
-
op (str): The comparison operator to use. Can be 'eq' (equal), '
|
|
544
|
-
'lt' (less than), or 'lte' (less than or equal).
|
|
684
|
+
op (str): The comparison operator to use. Can be 'eq' (equal), 'ne'/'neq' (not equal), 'gt' (greater than),
|
|
685
|
+
'gte' (greater than or equal), 'lt' (less than), or 'lte' (less than or equal).
|
|
545
686
|
field (str): The field to perform the comparison on.
|
|
546
687
|
value (float): The value to compare the field against.
|
|
547
688
|
|
|
548
689
|
Returns:
|
|
549
690
|
search (Search): The search object with the specified filter applied.
|
|
550
691
|
"""
|
|
551
|
-
if op
|
|
692
|
+
if op == "eq":
|
|
693
|
+
search = search.filter("term", **{field: value})
|
|
694
|
+
elif op == "ne" or op == "neq":
|
|
695
|
+
# For not equal, use a bool query with must_not
|
|
696
|
+
search = search.exclude("term", **{field: value})
|
|
697
|
+
elif op in ["gt", "gte", "lt", "lte"]:
|
|
698
|
+
# For range operators
|
|
552
699
|
key_filter = {field: {op: value}}
|
|
553
700
|
search = search.filter(Q("range", **key_filter))
|
|
554
|
-
|
|
701
|
+
elif op == "in":
|
|
702
|
+
# For in operator (value should be a list)
|
|
703
|
+
if isinstance(value, list):
|
|
704
|
+
search = search.filter("terms", **{field: value})
|
|
705
|
+
else:
|
|
706
|
+
search = search.filter("term", **{field: value})
|
|
707
|
+
elif op == "contains":
|
|
708
|
+
# For contains operator (for arrays)
|
|
555
709
|
search = search.filter("term", **{field: value})
|
|
556
710
|
|
|
557
711
|
return search
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.5.1"
|
{stac_fastapi_elasticsearch-6.4.0.dist-info → stac_fastapi_elasticsearch-6.5.1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: stac-fastapi-elasticsearch
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.5.1
|
|
4
4
|
Summary: An implementation of STAC API based on the FastAPI framework with both Elasticsearch and Opensearch.
|
|
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.5.1
|
|
19
|
+
Requires-Dist: sfeos-helpers==6.5.1
|
|
20
20
|
Requires-Dist: elasticsearch[async]~=8.18.0
|
|
21
21
|
Requires-Dist: uvicorn~=0.23.0
|
|
22
22
|
Requires-Dist: starlette<0.36.0,>=0.35.0
|
|
@@ -102,13 +102,13 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
|
|
|
102
102
|
## Table of Contents
|
|
103
103
|
|
|
104
104
|
- [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
|
|
105
|
-
- [Sponsors
|
|
105
|
+
- [Sponsors & Supporters](#sponsors--supporters)
|
|
106
106
|
- [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
|
|
107
107
|
- [Common Deployment Patterns](#common-deployment-patterns)
|
|
108
108
|
- [Technologies](#technologies)
|
|
109
109
|
- [Table of Contents](#table-of-contents)
|
|
110
110
|
- [Collection Search Extensions](#collection-search-extensions)
|
|
111
|
-
- [Documentation
|
|
111
|
+
- [Documentation & Resources](#documentation--resources)
|
|
112
112
|
- [Package Structure](#package-structure)
|
|
113
113
|
- [Examples](#examples)
|
|
114
114
|
- [Performance](#performance)
|
|
@@ -151,7 +151,11 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
|
|
|
151
151
|
|
|
152
152
|
## Collection Search Extensions
|
|
153
153
|
|
|
154
|
-
SFEOS
|
|
154
|
+
SFEOS provides enhanced collection search capabilities through two primary routes:
|
|
155
|
+
- **GET/POST `/collections`**: The standard STAC endpoint with extended query parameters
|
|
156
|
+
- **GET/POST `/collections-search`**: A custom endpoint that supports the same parameters, created to avoid conflicts with the STAC Transactions extension if enabled (which uses POST `/collections` for collection creation)
|
|
157
|
+
|
|
158
|
+
These endpoints support advanced collection discovery features including:
|
|
155
159
|
|
|
156
160
|
- **Sorting**: Sort collections by sortable fields using the `sortby` parameter
|
|
157
161
|
- Example: `/collections?sortby=+id` (ascending sort by ID)
|
|
@@ -167,9 +171,26 @@ SFEOS implements extended capabilities for the `/collections` endpoint, allowing
|
|
|
167
171
|
- Searches across multiple text fields including title, description, and keywords
|
|
168
172
|
- Supports partial word matching and relevance-based sorting
|
|
169
173
|
|
|
174
|
+
- **Structured Filtering**: Filter collections using CQL2 expressions
|
|
175
|
+
- JSON format: `/collections?filter={"op":"=","args":[{"property":"id"},"sentinel-2"]}&filter-lang=cql2-json`
|
|
176
|
+
- Text format: `/collections?filter=id='sentinel-2'&filter-lang=cql2-text` (note: string values must be quoted)
|
|
177
|
+
- Advanced text format: `/collections?filter=id LIKE '%sentinel%'&filter-lang=cql2-text` (supports LIKE, BETWEEN, etc.)
|
|
178
|
+
- Supports both CQL2 JSON and CQL2 text formats with various operators
|
|
179
|
+
- Enables precise filtering on any collection property
|
|
180
|
+
|
|
181
|
+
- **Datetime Filtering**: Filter collections by their temporal extent using the `datetime` parameter
|
|
182
|
+
- Example: `/collections?datetime=2020-01-01T00:00:00Z/2020-12-31T23:59:59Z` (finds collections with temporal extents that overlap this range)
|
|
183
|
+
- Example: `/collections?datetime=2020-06-15T12:00:00Z` (finds collections whose temporal extent includes this specific time)
|
|
184
|
+
- Example: `/collections?datetime=2020-01-01T00:00:00Z/..` (finds collections with temporal extents that extend to or beyond January 1, 2020)
|
|
185
|
+
- Example: `/collections?datetime=../2020-12-31T23:59:59Z` (finds collections with temporal extents that begin on or before December 31, 2020)
|
|
186
|
+
- Collections are matched if their temporal extent overlaps with the provided datetime parameter
|
|
187
|
+
- This allows for efficient discovery of collections based on time periods
|
|
188
|
+
|
|
170
189
|
These extensions make it easier to build user interfaces that display and navigate through collections efficiently.
|
|
171
190
|
|
|
172
|
-
> **Configuration**: Collection search extensions can be disabled by setting the `ENABLE_COLLECTIONS_SEARCH` environment variable to `false`. By default, these extensions are enabled.
|
|
191
|
+
> **Configuration**: Collection search extensions (sorting, field selection, free text search, structured filtering, and datetime filtering) for the `/collections` endpoint can be disabled by setting the `ENABLE_COLLECTIONS_SEARCH` environment variable to `false`. By default, these extensions are enabled.
|
|
192
|
+
>
|
|
193
|
+
> **Configuration**: The custom `/collections-search` endpoint can be enabled by setting the `ENABLE_COLLECTIONS_SEARCH_ROUTE` environment variable to `true`. By default, this endpoint is **disabled**.
|
|
173
194
|
|
|
174
195
|
> **Note**: Sorting is only available on fields that are indexed for sorting in Elasticsearch/OpenSearch. With the default mappings, you can sort on:
|
|
175
196
|
> - `id` (keyword field)
|
|
@@ -180,6 +201,7 @@ These extensions make it easier to build user interfaces that display and naviga
|
|
|
180
201
|
>
|
|
181
202
|
> **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.
|
|
182
203
|
|
|
204
|
+
|
|
183
205
|
## Package Structure
|
|
184
206
|
|
|
185
207
|
This project is organized into several packages, each with a specific purpose:
|
|
@@ -192,7 +214,7 @@ This project is organized into several packages, each with a specific purpose:
|
|
|
192
214
|
- Shared logic and utilities that improve code reuse between backends
|
|
193
215
|
|
|
194
216
|
- **stac_fastapi_elasticsearch**: Complete implementation of the STAC API using Elasticsearch as the backend database. This package depends on both `stac_fastapi_core` and `sfeos_helpers`.
|
|
195
|
-
|
|
217
|
+
|
|
196
218
|
- **stac_fastapi_opensearch**: Complete implementation of the STAC API using OpenSearch as the backend database. This package depends on both `stac_fastapi_core` and `sfeos_helpers`.
|
|
197
219
|
|
|
198
220
|
## Examples
|
|
@@ -310,12 +332,13 @@ You can customize additional settings in your `.env` file:
|
|
|
310
332
|
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
|
|
311
333
|
| `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 |
|
|
312
334
|
| `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 |
|
|
313
|
-
| `ENABLE_COLLECTIONS_SEARCH` | Enable collection search extensions (sort, fields).
|
|
314
|
-
| `
|
|
335
|
+
| `ENABLE_COLLECTIONS_SEARCH` | Enable collection search extensions (sort, fields, free text search, structured filtering, and datetime filtering) on the core `/collections` endpoint. | `true` | Optional |
|
|
336
|
+
| `ENABLE_COLLECTIONS_SEARCH_ROUTE` | Enable the custom `/collections-search` endpoint (both GET and POST methods). When disabled, the custom endpoint will not be available, but collection search extensions will still be available on the core `/collections` endpoint if `ENABLE_COLLECTIONS_SEARCH` is true. | `false` | Optional |
|
|
337
|
+
| `ENABLE_TRANSACTIONS_EXTENSIONS` | Enables or disables the Transactions and Bulk Transactions API extensions. This is useful for deployments where mutating the catalog via the API should be prevented. If set to `true`, the POST `/collections` route for search will be unavailable in the API. | `true` | Optional |
|
|
315
338
|
| `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
|
|
316
339
|
| `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 |
|
|
317
340
|
| `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 |
|
|
318
|
-
| `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. |
|
|
341
|
+
| `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 |
|
|
319
342
|
|
|
320
343
|
> [!NOTE]
|
|
321
344
|
> 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.
|
|
@@ -461,7 +484,6 @@ The system uses a precise naming convention:
|
|
|
461
484
|
- `ENABLE_COLLECTIONS_SEARCH`: Set to `true` (default) to enable collection search extensions (sort, fields). Set to `false` to disable.
|
|
462
485
|
- `ENABLE_TRANSACTIONS_EXTENSIONS`: Set to `true` (default) to enable transaction extensions. Set to `false` to disable.
|
|
463
486
|
|
|
464
|
-
|
|
465
487
|
## Collection Pagination
|
|
466
488
|
|
|
467
489
|
- **Overview**: The collections route supports pagination through optional query parameters.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
stac_fastapi/elasticsearch/__init__.py,sha256=w_MZutYLreNV372sCuO46bPb0TngmPs4u8737ueS0wE,31
|
|
2
|
+
stac_fastapi/elasticsearch/app.py,sha256=024U5xvXmSWUJABS9SekbqqamhsNSpqPBII2NBWktz8,9979
|
|
3
|
+
stac_fastapi/elasticsearch/config.py,sha256=itvPYr4TiOg9pWQrycgGaQxQ_Vc2KKP3aHdtH0OUZvw,5322
|
|
4
|
+
stac_fastapi/elasticsearch/database_logic.py,sha256=eNPzK_dBhNJgAgqEpF3hHmqep_7_KGpLVEQA-jusEAY,70183
|
|
5
|
+
stac_fastapi/elasticsearch/version.py,sha256=FuGC3fKnAmD4Wk95swJ6qCVBs5mZiShrlRKuSH-voyE,45
|
|
6
|
+
stac_fastapi_elasticsearch-6.5.1.dist-info/METADATA,sha256=JFd5dRXZznzO6uqbUGS1wVL5YbkyNorypBCQ2T-KVA8,42084
|
|
7
|
+
stac_fastapi_elasticsearch-6.5.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
8
|
+
stac_fastapi_elasticsearch-6.5.1.dist-info/entry_points.txt,sha256=aCKixki0LpUl64UPsPMtiNvfdyq-QsTCxVjJ54VF6Jk,82
|
|
9
|
+
stac_fastapi_elasticsearch-6.5.1.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
|
|
10
|
+
stac_fastapi_elasticsearch-6.5.1.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
stac_fastapi/elasticsearch/__init__.py,sha256=w_MZutYLreNV372sCuO46bPb0TngmPs4u8737ueS0wE,31
|
|
2
|
-
stac_fastapi/elasticsearch/app.py,sha256=BXoewlnMDHU6bwBJY9bV02Gho-ZzmIyESMOUWPMuR9k,7506
|
|
3
|
-
stac_fastapi/elasticsearch/config.py,sha256=itvPYr4TiOg9pWQrycgGaQxQ_Vc2KKP3aHdtH0OUZvw,5322
|
|
4
|
-
stac_fastapi/elasticsearch/database_logic.py,sha256=6dvex-sEMsl3rBnfTKYey7gyG63wq6KPzOUYRM20r4I,63574
|
|
5
|
-
stac_fastapi/elasticsearch/version.py,sha256=1xfRb7s1Stv055oEewNJpZn9OQUUcaJvgSzCsYLHYSQ,45
|
|
6
|
-
stac_fastapi_elasticsearch-6.4.0.dist-info/METADATA,sha256=-kQ6Zw1DMLH7WOvdtMCqsWbrC7HxnZ1Jp9xPVpJrqg0,39686
|
|
7
|
-
stac_fastapi_elasticsearch-6.4.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
8
|
-
stac_fastapi_elasticsearch-6.4.0.dist-info/entry_points.txt,sha256=aCKixki0LpUl64UPsPMtiNvfdyq-QsTCxVjJ54VF6Jk,82
|
|
9
|
-
stac_fastapi_elasticsearch-6.4.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
|
|
10
|
-
stac_fastapi_elasticsearch-6.4.0.dist-info/RECORD,,
|
{stac_fastapi_elasticsearch-6.4.0.dist-info → stac_fastapi_elasticsearch-6.5.1.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|