stac-fastapi-opensearch 6.4.0__py3-none-any.whl → 6.5.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.
@@ -23,14 +23,19 @@ 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
29
32
  from stac_fastapi.core.session import Session
30
33
  from stac_fastapi.core.utilities import get_bool_env
31
- from stac_fastapi.extensions.core import ( # CollectionSearchFilterExtension,
34
+ from stac_fastapi.extensions.core import (
32
35
  AggregationExtension,
33
36
  CollectionSearchExtension,
37
+ CollectionSearchFilterExtension,
38
+ CollectionSearchPostExtension,
34
39
  FilterExtension,
35
40
  FreeTextExtension,
36
41
  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 = OpensearchSettings()
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
- # Create collection search extensions if enabled
120
- if ENABLE_COLLECTIONS_SEARCH:
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
- # QueryExtension(conformance_classes=[QueryConformanceClasses.COLLECTIONS]),
136
+ QueryExtension(conformance_classes=[QueryConformanceClasses.COLLECTIONS]),
124
137
  SortExtension(conformance_classes=[SortConformanceClasses.COLLECTIONS]),
125
138
  FieldsExtension(conformance_classes=[FieldsConformanceClasses.COLLECTIONS]),
126
- # CollectionSearchFilterExtension(
127
- # conformance_classes=[FilterConformanceClasses.COLLECTIONS]
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
 
@@ -169,14 +233,15 @@ app_config = {
169
233
  post_request_model=post_request_model,
170
234
  landing_page_id=os.getenv("STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi"),
171
235
  ),
236
+ "collections_get_request_model": collections_get_request_model,
172
237
  "search_get_request_model": create_get_request_model(search_extensions),
173
238
  "search_post_request_model": post_request_model,
174
239
  "items_get_request_model": items_get_request_model,
175
240
  "route_dependencies": get_route_dependencies(),
176
241
  }
177
242
 
178
- # Add collections_get_request_model if collection search is enabled
179
- if ENABLE_COLLECTIONS_SEARCH:
243
+ # Add collections_get_request_model if it was created
244
+ if collections_get_request_model:
180
245
  app_config["collections_get_request_model"] = collections_get_request_model
181
246
 
182
247
  api = StacApi(**app_config)
@@ -160,8 +160,11 @@ class DatabaseLogic(BaseDatabaseLogic):
160
160
  request: Request,
161
161
  sort: Optional[List[Dict[str, Any]]] = None,
162
162
  q: Optional[List[str]] = None,
163
- ) -> Tuple[List[Dict[str, Any]], Optional[str]]:
164
- """Retrieve a list of collections from Elasticsearch, supporting pagination.
163
+ filter: Optional[Dict[str, Any]] = None,
164
+ query: Optional[Dict[str, Dict[str, Any]]] = None,
165
+ datetime: Optional[str] = None,
166
+ ) -> Tuple[List[Dict[str, Any]], Optional[str], Optional[int]]:
167
+ """Retrieve a list of collections from OpenSearch, supporting pagination.
165
168
 
166
169
  Args:
167
170
  token (Optional[str]): The pagination token.
@@ -169,6 +172,9 @@ class DatabaseLogic(BaseDatabaseLogic):
169
172
  request (Request): The FastAPI request object.
170
173
  sort (Optional[List[Dict[str, Any]]]): Optional sort parameter from the request.
171
174
  q (Optional[List[str]]): Free text search terms.
175
+ query (Optional[Dict[str, Dict[str, Any]]]): Query extension parameters.
176
+ filter (Optional[Dict[str, Any]]): Structured query in CQL2 format.
177
+ datetime (Optional[str]): Temporal filter.
172
178
 
173
179
  Returns:
174
180
  A tuple of (collections, next pagination token if any).
@@ -206,8 +212,24 @@ class DatabaseLogic(BaseDatabaseLogic):
206
212
  "size": limit,
207
213
  }
208
214
 
215
+ # Handle search_after token - split by '|' to get all sort values
216
+ search_after = None
209
217
  if token:
210
- body["search_after"] = [token]
218
+ try:
219
+ # The token should be a pipe-separated string of sort values
220
+ # e.g., "2023-01-01T00:00:00Z|collection-1"
221
+ search_after = token.split("|")
222
+ # If the number of sort fields doesn't match token parts, ignore the token
223
+ if len(search_after) != len(formatted_sort):
224
+ search_after = None
225
+ except Exception:
226
+ search_after = None
227
+
228
+ if search_after is not None:
229
+ body["search_after"] = search_after
230
+
231
+ # Build the query part of the body
232
+ query_parts = []
211
233
 
212
234
  # Apply free text query if provided
213
235
  if q:
@@ -235,16 +257,87 @@ class DatabaseLogic(BaseDatabaseLogic):
235
257
  }
236
258
  )
237
259
 
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
- }
260
+ # Add the free text query to the query parts
261
+ query_parts.append(
262
+ {"bool": {"should": should_clauses, "minimum_should_match": 1}}
263
+ )
242
264
 
243
- response = await self.client.search(
244
- index=COLLECTIONS_INDEX,
245
- body=body,
265
+ # Apply structured filter if provided
266
+ if filter:
267
+ # Convert string filter to dict if needed
268
+ if isinstance(filter, str):
269
+ filter = orjson.loads(filter)
270
+ # Convert the filter to an OpenSearch query using the filter module
271
+ es_query = filter_module.to_es(await self.get_queryables_mapping(), filter)
272
+ query_parts.append(es_query)
273
+
274
+ # Apply query extension if provided
275
+ if query:
276
+ try:
277
+ # First create a search object to apply filters
278
+ search = Search(index=COLLECTIONS_INDEX)
279
+
280
+ # Process each field and operator in the query
281
+ for field_name, expr in query.items():
282
+ for op, value in expr.items():
283
+ # For collections, we don't need to prefix with 'properties__'
284
+ field = field_name
285
+ # Apply the filter using apply_stacql_filter
286
+ search = self.apply_stacql_filter(
287
+ search=search, op=op, field=field, value=value
288
+ )
289
+
290
+ # Convert the search object to a query dict and add it to query_parts
291
+ search_dict = search.to_dict()
292
+ if "query" in search_dict:
293
+ query_parts.append(search_dict["query"])
294
+
295
+ except Exception as e:
296
+ logger = logging.getLogger(__name__)
297
+ logger.error(f"Error converting query to OpenSearch: {e}")
298
+ # If there's an error, add a query that matches nothing
299
+ query_parts.append({"bool": {"must_not": {"match_all": {}}}})
300
+ raise
301
+
302
+ # Combine all query parts with AND logic if there are multiple
303
+ datetime_filter = None
304
+ if datetime:
305
+ datetime_filter = self._apply_collection_datetime_filter(datetime)
306
+ if datetime_filter:
307
+ query_parts.append(datetime_filter)
308
+
309
+ # Combine all query parts with AND logic
310
+ if query_parts:
311
+ body["query"] = (
312
+ query_parts[0]
313
+ if len(query_parts) == 1
314
+ else {"bool": {"must": query_parts}}
315
+ )
316
+
317
+ # Create a copy of the body for count query (without pagination and sorting)
318
+ count_body = body.copy()
319
+ if "search_after" in count_body:
320
+ del count_body["search_after"]
321
+ count_body["size"] = 0
322
+
323
+ # Create async tasks for both search and count
324
+ search_task = asyncio.create_task(
325
+ self.client.search(
326
+ index=COLLECTIONS_INDEX,
327
+ body=body,
328
+ )
246
329
  )
247
330
 
331
+ count_task = asyncio.create_task(
332
+ self.client.count(
333
+ index=COLLECTIONS_INDEX,
334
+ body={"query": body.get("query", {"match_all": {}})},
335
+ )
336
+ )
337
+
338
+ # Wait for search task to complete
339
+ response = await search_task
340
+
248
341
  hits = response["hits"]["hits"]
249
342
  collections = [
250
343
  self.collection_serializer.db_to_stac(
@@ -255,12 +348,27 @@ class DatabaseLogic(BaseDatabaseLogic):
255
348
 
256
349
  next_token = None
257
350
  if len(hits) == limit:
258
- # Ensure we have a valid sort value for next_token
259
351
  next_token_values = hits[-1].get("sort")
260
352
  if next_token_values:
261
- next_token = next_token_values[0]
353
+ # Join all sort values with '|' to create the token
354
+ next_token = "|".join(str(val) for val in next_token_values)
262
355
 
263
- return collections, next_token
356
+ # Get the total count of collections
357
+ matched = (
358
+ response["hits"]["total"]["value"]
359
+ if response["hits"]["total"]["relation"] == "eq"
360
+ else None
361
+ )
362
+
363
+ # If count task is done, use its result
364
+ if count_task.done():
365
+ try:
366
+ matched = count_task.result().get("count")
367
+ except Exception as e:
368
+ logger = logging.getLogger(__name__)
369
+ logger.error(f"Count task failed: {e}")
370
+
371
+ return collections, next_token, matched
264
372
 
265
373
  async def get_one_item(self, collection_id: str, item_id: str) -> Dict:
266
374
  """Retrieve a single item from the database.
@@ -276,7 +384,7 @@ class DatabaseLogic(BaseDatabaseLogic):
276
384
  NotFoundError: If the specified Item does not exist in the Collection.
277
385
 
278
386
  Notes:
279
- The Item is retrieved from the Elasticsearch database using the `client.get` method,
387
+ The Item is retrieved from the Opensearch database using the `client.get` method,
280
388
  with the index for the Collection as the target index and the combined `mk_item_id` as the document id.
281
389
  """
282
390
  try:
@@ -348,6 +456,41 @@ class DatabaseLogic(BaseDatabaseLogic):
348
456
  search=search, free_text_queries=free_text_queries
349
457
  )
350
458
 
459
+ @staticmethod
460
+ def _apply_collection_datetime_filter(
461
+ datetime_str: Optional[str],
462
+ ) -> Optional[Dict[str, Any]]:
463
+ """Create a temporal filter for collections based on their extent."""
464
+ if not datetime_str:
465
+ return None
466
+
467
+ # Parse the datetime string into start and end
468
+ if "/" in datetime_str:
469
+ start, end = datetime_str.split("/")
470
+ # Replace open-ended ranges with concrete dates
471
+ if start == "..":
472
+ # For open-ended start, use a very early date
473
+ start = "1800-01-01T00:00:00Z"
474
+ if end == "..":
475
+ # For open-ended end, use a far future date
476
+ end = "2999-12-31T23:59:59Z"
477
+ else:
478
+ # If it's just a single date, use it for both start and end
479
+ start = end = datetime_str
480
+
481
+ return {
482
+ "bool": {
483
+ "must": [
484
+ # Check if any date in the array is less than or equal to the query end date
485
+ # This will match if the collection's start date is before or equal to the query end date
486
+ {"range": {"extent.temporal.interval": {"lte": end}}},
487
+ # Check if any date in the array is greater than or equal to the query start date
488
+ # This will match if the collection's end date is after or equal to the query start date
489
+ {"range": {"extent.temporal.interval": {"gte": start}}},
490
+ ]
491
+ }
492
+ }
493
+
351
494
  @staticmethod
352
495
  def apply_datetime_filter(
353
496
  search: Search, datetime: Optional[str]
@@ -542,18 +685,31 @@ class DatabaseLogic(BaseDatabaseLogic):
542
685
 
543
686
  Args:
544
687
  search (Search): The search object to apply the filter to.
545
- op (str): The comparison operator to use. Can be 'eq' (equal), 'gt' (greater than), 'gte' (greater than or equal),
546
- 'lt' (less than), or 'lte' (less than or equal).
688
+ op (str): The comparison operator to use. Can be 'eq' (equal), 'ne'/'neq' (not equal), 'gt' (greater than),
689
+ 'gte' (greater than or equal), 'lt' (less than), or 'lte' (less than or equal).
547
690
  field (str): The field to perform the comparison on.
548
691
  value (float): The value to compare the field against.
549
692
 
550
693
  Returns:
551
694
  search (Search): The search object with the specified filter applied.
552
695
  """
553
- if op != "eq":
554
- key_filter = {field: {f"{op}": value}}
696
+ if op == "eq":
697
+ search = search.filter("term", **{field: value})
698
+ elif op == "ne" or op == "neq":
699
+ # For not equal, use a bool query with must_not
700
+ search = search.exclude("term", **{field: value})
701
+ elif op in ["gt", "gte", "lt", "lte"]:
702
+ # For range operators
703
+ key_filter = {field: {op: value}}
555
704
  search = search.filter(Q("range", **key_filter))
556
- else:
705
+ elif op == "in":
706
+ # For in operator (value should be a list)
707
+ if isinstance(value, list):
708
+ search = search.filter("terms", **{field: value})
709
+ else:
710
+ search = search.filter("term", **{field: value})
711
+ elif op == "contains":
712
+ # For contains operator (for arrays)
557
713
  search = search.filter("term", **{field: value})
558
714
 
559
715
  return search
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "6.4.0"
2
+ __version__ = "6.5.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi-opensearch
3
- Version: 6.4.0
3
+ Version: 6.5.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.4.0
19
- Requires-Dist: sfeos-helpers==6.4.0
18
+ Requires-Dist: stac-fastapi-core==6.5.0
19
+ Requires-Dist: sfeos-helpers==6.5.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
@@ -103,13 +103,13 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
103
103
  ## Table of Contents
104
104
 
105
105
  - [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
106
- - [Sponsors \& Supporters](#sponsors--supporters)
106
+ - [Sponsors & Supporters](#sponsors--supporters)
107
107
  - [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
108
108
  - [Common Deployment Patterns](#common-deployment-patterns)
109
109
  - [Technologies](#technologies)
110
110
  - [Table of Contents](#table-of-contents)
111
111
  - [Collection Search Extensions](#collection-search-extensions)
112
- - [Documentation \& Resources](#documentation--resources)
112
+ - [Documentation & Resources](#documentation--resources)
113
113
  - [Package Structure](#package-structure)
114
114
  - [Examples](#examples)
115
115
  - [Performance](#performance)
@@ -152,7 +152,11 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
152
152
 
153
153
  ## Collection Search Extensions
154
154
 
155
- SFEOS implements extended capabilities for the `/collections` endpoint, allowing for more powerful collection discovery:
155
+ SFEOS provides enhanced collection search capabilities through two primary routes:
156
+ - **GET/POST `/collections`**: The standard STAC endpoint with extended query parameters
157
+ - **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)
158
+
159
+ These endpoints support advanced collection discovery features including:
156
160
 
157
161
  - **Sorting**: Sort collections by sortable fields using the `sortby` parameter
158
162
  - Example: `/collections?sortby=+id` (ascending sort by ID)
@@ -168,9 +172,26 @@ SFEOS implements extended capabilities for the `/collections` endpoint, allowing
168
172
  - Searches across multiple text fields including title, description, and keywords
169
173
  - Supports partial word matching and relevance-based sorting
170
174
 
175
+ - **Structured Filtering**: Filter collections using CQL2 expressions
176
+ - JSON format: `/collections?filter={"op":"=","args":[{"property":"id"},"sentinel-2"]}&filter-lang=cql2-json`
177
+ - Text format: `/collections?filter=id='sentinel-2'&filter-lang=cql2-text` (note: string values must be quoted)
178
+ - Advanced text format: `/collections?filter=id LIKE '%sentinel%'&filter-lang=cql2-text` (supports LIKE, BETWEEN, etc.)
179
+ - Supports both CQL2 JSON and CQL2 text formats with various operators
180
+ - Enables precise filtering on any collection property
181
+
182
+ - **Datetime Filtering**: Filter collections by their temporal extent using the `datetime` parameter
183
+ - Example: `/collections?datetime=2020-01-01T00:00:00Z/2020-12-31T23:59:59Z` (finds collections with temporal extents that overlap this range)
184
+ - Example: `/collections?datetime=2020-06-15T12:00:00Z` (finds collections whose temporal extent includes this specific time)
185
+ - Example: `/collections?datetime=2020-01-01T00:00:00Z/..` (finds collections with temporal extents that extend to or beyond January 1, 2020)
186
+ - Example: `/collections?datetime=../2020-12-31T23:59:59Z` (finds collections with temporal extents that begin on or before December 31, 2020)
187
+ - Collections are matched if their temporal extent overlaps with the provided datetime parameter
188
+ - This allows for efficient discovery of collections based on time periods
189
+
171
190
  These extensions make it easier to build user interfaces that display and navigate through collections efficiently.
172
191
 
173
- > **Configuration**: Collection search extensions can be disabled by setting the `ENABLE_COLLECTIONS_SEARCH` environment variable to `false`. By default, these extensions are enabled.
192
+ > **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.
193
+ >
194
+ > **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**.
174
195
 
175
196
  > **Note**: Sorting is only available on fields that are indexed for sorting in Elasticsearch/OpenSearch. With the default mappings, you can sort on:
176
197
  > - `id` (keyword field)
@@ -181,6 +202,7 @@ These extensions make it easier to build user interfaces that display and naviga
181
202
  >
182
203
  > **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
204
 
205
+
184
206
  ## Package Structure
185
207
 
186
208
  This project is organized into several packages, each with a specific purpose:
@@ -193,7 +215,7 @@ This project is organized into several packages, each with a specific purpose:
193
215
  - Shared logic and utilities that improve code reuse between backends
194
216
 
195
217
  - **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`.
196
- -
218
+
197
219
  - **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`.
198
220
 
199
221
  ## Examples
@@ -311,12 +333,13 @@ You can customize additional settings in your `.env` file:
311
333
  | `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
312
334
  | `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 |
313
335
  | `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 |
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 |
336
+ | `ENABLE_COLLECTIONS_SEARCH` | Enable collection search extensions (sort, fields, free text search, structured filtering, and datetime filtering) on the core `/collections` endpoint. | `true` | Optional |
337
+ | `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 |
338
+ | `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 |
316
339
  | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
317
340
  | `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 |
318
341
  | `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 |
342
+ | `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 |
320
343
 
321
344
  > [!NOTE]
322
345
  > 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.
@@ -462,7 +485,6 @@ The system uses a precise naming convention:
462
485
  - `ENABLE_COLLECTIONS_SEARCH`: Set to `true` (default) to enable collection search extensions (sort, fields). Set to `false` to disable.
463
486
  - `ENABLE_TRANSACTIONS_EXTENSIONS`: Set to `true` (default) to enable transaction extensions. Set to `false` to disable.
464
487
 
465
-
466
488
  ## Collection Pagination
467
489
 
468
490
  - **Overview**: The collections route supports pagination through optional query parameters.
@@ -0,0 +1,10 @@
1
+ stac_fastapi/opensearch/__init__.py,sha256=iJWMUgn7mUvmuPQSO_FlyhJ5eDdbbfmGv1qnFOX5-qk,28
2
+ stac_fastapi/opensearch/app.py,sha256=paRSzdkT3yxkegC3KEg98CA7PpNQ0C2LW0Mozb_LcP0,10025
3
+ stac_fastapi/opensearch/config.py,sha256=zGx4-4c5zEnu_Bh8Ra3SkIC83tluDiz-wKYQclRRDJA,5179
4
+ stac_fastapi/opensearch/database_logic.py,sha256=8R40AdapytllTsc9xxtoDuTYa5jubc388SxevSZnfy0,67923
5
+ stac_fastapi/opensearch/version.py,sha256=KQjuGSR03-CXgF6wsaZ8qsni161S2BjhOn3wTX8JAMw,45
6
+ stac_fastapi_opensearch-6.5.0.dist-info/METADATA,sha256=SM_fV6pr5zUPeK9u4GOhbjBkD4dmi08PWz3mZnAK3zk,42048
7
+ stac_fastapi_opensearch-6.5.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
8
+ stac_fastapi_opensearch-6.5.0.dist-info/entry_points.txt,sha256=zjZ0Xsr9BUNJqMkdPpl6zEIUykv1uFdJtNELFRChp0w,76
9
+ stac_fastapi_opensearch-6.5.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
10
+ stac_fastapi_opensearch-6.5.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=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,,