stac-fastapi-core 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/core/core.py CHANGED
@@ -136,6 +136,20 @@ class CoreClient(AsyncBaseCoreClient):
136
136
  "href": urljoin(base_url, "search"),
137
137
  "method": "POST",
138
138
  },
139
+ {
140
+ "rel": "collections-search",
141
+ "type": "application/json",
142
+ "title": "Collections Search",
143
+ "href": urljoin(base_url, "collections-search"),
144
+ "method": "GET",
145
+ },
146
+ {
147
+ "rel": "collections-search",
148
+ "type": "application/json",
149
+ "title": "Collections Search",
150
+ "href": urljoin(base_url, "collections-search"),
151
+ "method": "POST",
152
+ },
139
153
  ],
140
154
  stac_extensions=extension_schemas,
141
155
  )
@@ -226,35 +240,78 @@ class CoreClient(AsyncBaseCoreClient):
226
240
 
227
241
  async def all_collections(
228
242
  self,
243
+ limit: Optional[int] = None,
244
+ datetime: Optional[str] = None,
229
245
  fields: Optional[List[str]] = None,
230
- sortby: Optional[str] = None,
246
+ sortby: Optional[Union[str, List[str]]] = None,
247
+ filter_expr: Optional[str] = None,
248
+ filter_lang: Optional[str] = None,
231
249
  q: Optional[Union[str, List[str]]] = None,
250
+ query: Optional[str] = None,
251
+ request: Request = None,
252
+ token: Optional[str] = None,
232
253
  **kwargs,
233
254
  ) -> stac_types.Collections:
234
255
  """Read all collections from the database.
235
256
 
236
257
  Args:
258
+ datetime (Optional[str]): Filter collections by datetime range.
259
+ limit (Optional[int]): Maximum number of collections to return.
237
260
  fields (Optional[List[str]]): Fields to include or exclude from the results.
238
261
  sortby (Optional[str]): Sorting options for the results.
239
- q (Optional[List[str]]): Free text search terms.
262
+ filter_expr (Optional[str]): Structured filter expression in CQL2 JSON or CQL2-text format.
263
+ query (Optional[str]): Legacy query parameter (deprecated).
264
+ filter_lang (Optional[str]): Must be 'cql2-json' or 'cql2-text' if specified, other values will result in an error.
265
+ q (Optional[Union[str, List[str]]]): Free text search terms.
240
266
  **kwargs: Keyword arguments from the request.
241
267
 
242
268
  Returns:
243
269
  A Collections object containing all the collections in the database and links to various resources.
244
270
  """
245
- request = kwargs["request"]
246
271
  base_url = str(request.base_url)
247
- limit = int(request.query_params.get("limit", os.getenv("STAC_ITEM_LIMIT", 10)))
248
- token = request.query_params.get("token")
272
+
273
+ # Get the global limit from environment variable
274
+ global_limit = None
275
+ env_limit = os.getenv("STAC_ITEM_LIMIT")
276
+ if env_limit:
277
+ try:
278
+ global_limit = int(env_limit)
279
+ except ValueError:
280
+ # Handle invalid integer in environment variable
281
+ pass
282
+
283
+ # Apply global limit if it exists
284
+ if global_limit is not None:
285
+ # If a limit was provided, use the smaller of the two
286
+ if limit is not None:
287
+ limit = min(limit, global_limit)
288
+ else:
289
+ limit = global_limit
290
+ else:
291
+ # No global limit, use provided limit or default
292
+ if limit is None:
293
+ query_limit = request.query_params.get("limit")
294
+ if query_limit:
295
+ try:
296
+ limit = int(query_limit)
297
+ except ValueError:
298
+ limit = 10
299
+ else:
300
+ limit = 10
301
+
302
+ # Get token from query params only if not already provided (for GET requests)
303
+ if token is None:
304
+ token = request.query_params.get("token")
249
305
 
250
306
  # Process fields parameter for filtering collection properties
251
307
  includes, excludes = set(), set()
252
- if fields and self.extension_is_enabled("FieldsExtension"):
308
+ if fields:
253
309
  for field in fields:
254
310
  if field[0] == "-":
255
311
  excludes.add(field[1:])
256
312
  else:
257
- includes.add(field[1:] if field[0] in "+ " else field)
313
+ include_field = field[1:] if field[0] in "+ " else field
314
+ includes.add(include_field)
258
315
 
259
316
  sort = None
260
317
  if sortby:
@@ -276,12 +333,82 @@ class CoreClient(AsyncBaseCoreClient):
276
333
  if q is not None:
277
334
  q_list = [q] if isinstance(q, str) else q
278
335
 
279
- collections, next_token = await self.database.get_all_collections(
280
- token=token, limit=limit, request=request, sort=sort, q=q_list
336
+ # Parse the query parameter if provided
337
+ parsed_query = None
338
+ if query is not None:
339
+ try:
340
+ parsed_query = orjson.loads(query)
341
+ except Exception as e:
342
+ raise HTTPException(
343
+ status_code=400, detail=f"Invalid query parameter: {e}"
344
+ )
345
+
346
+ # Parse the filter parameter if provided
347
+ parsed_filter = None
348
+ if filter_expr is not None:
349
+ try:
350
+ # Only raise an error for explicitly unsupported filter languages
351
+ if filter_lang is not None and filter_lang not in [
352
+ "cql2-json",
353
+ "cql2-text",
354
+ ]:
355
+ # Raise an error for unsupported filter languages
356
+ raise HTTPException(
357
+ status_code=400,
358
+ detail=f"Only 'cql2-json' and 'cql2-text' filter languages are supported for collections. Got '{filter_lang}'.",
359
+ )
360
+
361
+ # Handle different filter formats
362
+ try:
363
+ if filter_lang == "cql2-text" or filter_lang is None:
364
+ # For cql2-text or when no filter_lang is specified, try both formats
365
+ try:
366
+ # First try to parse as JSON
367
+ parsed_filter = orjson.loads(unquote_plus(filter_expr))
368
+ except Exception:
369
+ # If that fails, use pygeofilter to convert CQL2-text to CQL2-JSON
370
+ try:
371
+ # Parse CQL2-text and convert to CQL2-JSON
372
+ text_filter = unquote_plus(filter_expr)
373
+ parsed_ast = parse_cql2_text(text_filter)
374
+ parsed_filter = to_cql2(parsed_ast)
375
+ except Exception as e:
376
+ # If parsing fails, provide a helpful error message
377
+ raise HTTPException(
378
+ status_code=400,
379
+ detail=f"Invalid CQL2-text filter: {e}. Please check your syntax.",
380
+ )
381
+ else:
382
+ # For explicit cql2-json, parse as JSON
383
+ parsed_filter = orjson.loads(unquote_plus(filter_expr))
384
+ except Exception as e:
385
+ # Catch any other parsing errors
386
+ raise HTTPException(
387
+ status_code=400, detail=f"Error parsing filter: {e}"
388
+ )
389
+
390
+ except Exception as e:
391
+ raise HTTPException(
392
+ status_code=400, detail=f"Invalid filter parameter: {e}"
393
+ )
394
+
395
+ parsed_datetime = None
396
+ if datetime:
397
+ parsed_datetime = format_datetime_range(date_str=datetime)
398
+
399
+ collections, next_token, maybe_count = await self.database.get_all_collections(
400
+ token=token,
401
+ limit=limit,
402
+ request=request,
403
+ sort=sort,
404
+ q=q_list,
405
+ filter=parsed_filter,
406
+ query=parsed_query,
407
+ datetime=parsed_datetime,
281
408
  )
282
409
 
283
410
  # Apply field filtering if fields parameter was provided
284
- if fields and self.extension_is_enabled("FieldsExtension"):
411
+ if fields:
285
412
  filtered_collections = [
286
413
  filter_fields(collection, includes, excludes)
287
414
  for collection in collections
@@ -303,7 +430,95 @@ class CoreClient(AsyncBaseCoreClient):
303
430
  next_link = PagingLinks(next=next_token, request=request).link_next()
304
431
  links.append(next_link)
305
432
 
306
- return stac_types.Collections(collections=filtered_collections, links=links)
433
+ return stac_types.Collections(
434
+ collections=filtered_collections,
435
+ links=links,
436
+ numberMatched=maybe_count,
437
+ numberReturned=len(filtered_collections),
438
+ )
439
+
440
+ async def post_all_collections(
441
+ self, search_request: BaseSearchPostRequest, request: Request, **kwargs
442
+ ) -> stac_types.Collections:
443
+ """Search collections with POST request.
444
+
445
+ Args:
446
+ search_request (BaseSearchPostRequest): The search request.
447
+ request (Request): The request.
448
+
449
+ Returns:
450
+ A Collections object containing all the collections in the database and links to various resources.
451
+ """
452
+ request.postbody = search_request.model_dump(exclude_unset=True)
453
+
454
+ fields = None
455
+
456
+ # Check for field attribute (ExtendedSearch format)
457
+ if hasattr(search_request, "field") and search_request.field:
458
+ fields = []
459
+
460
+ # Handle include fields
461
+ if (
462
+ hasattr(search_request.field, "includes")
463
+ and search_request.field.includes
464
+ ):
465
+ for field in search_request.field.includes:
466
+ fields.append(f"+{field}")
467
+
468
+ # Handle exclude fields
469
+ if (
470
+ hasattr(search_request.field, "excludes")
471
+ and search_request.field.excludes
472
+ ):
473
+ for field in search_request.field.excludes:
474
+ fields.append(f"-{field}")
475
+
476
+ # Convert sortby parameter from POST format to all_collections format
477
+ sortby = None
478
+ # Check for sortby attribute
479
+ if hasattr(search_request, "sortby") and search_request.sortby:
480
+ # Create a list of sort strings in the format expected by all_collections
481
+ sortby = []
482
+ for sort_item in search_request.sortby:
483
+ # Handle different types of sort items
484
+ if hasattr(sort_item, "field") and hasattr(sort_item, "direction"):
485
+ # This is a Pydantic model with field and direction attributes
486
+ field = sort_item.field
487
+ direction = sort_item.direction
488
+ elif isinstance(sort_item, dict):
489
+ # This is a dictionary with field and direction keys
490
+ field = sort_item.get("field")
491
+ direction = sort_item.get("direction", "asc")
492
+ else:
493
+ # Skip this item if we can't extract field and direction
494
+ continue
495
+
496
+ if field:
497
+ # Create a sort string in the format expected by all_collections
498
+ # e.g., "-id" for descending sort on id field
499
+ prefix = "-" if direction.lower() == "desc" else ""
500
+ sortby.append(f"{prefix}{field}")
501
+
502
+ # Pass all parameters from search_request to all_collections
503
+ return await self.all_collections(
504
+ limit=search_request.limit if hasattr(search_request, "limit") else None,
505
+ datetime=search_request.datetime
506
+ if hasattr(search_request, "datetime")
507
+ else None,
508
+ token=search_request.token if hasattr(search_request, "token") else None,
509
+ fields=fields,
510
+ sortby=sortby,
511
+ filter_expr=search_request.filter
512
+ if hasattr(search_request, "filter")
513
+ else None,
514
+ filter_lang=search_request.filter_lang
515
+ if hasattr(search_request, "filter_lang")
516
+ else None,
517
+ query=search_request.query if hasattr(search_request, "query") else None,
518
+ q=search_request.q if hasattr(search_request, "q") else None,
519
+ request=request,
520
+ **kwargs,
521
+ )
307
522
 
308
523
  async def get_collection(
309
524
  self, collection_id: str, **kwargs
@@ -621,11 +836,7 @@ class CoreClient(AsyncBaseCoreClient):
621
836
  datetime_search=datetime_search,
622
837
  )
623
838
 
624
- fields = (
625
- getattr(search_request, "fields", None)
626
- if self.extension_is_enabled("FieldsExtension")
627
- else None
628
- )
839
+ fields = getattr(search_request, "fields", None)
629
840
  include: Set[str] = fields.include if fields and fields.include else set()
630
841
  exclude: Set[str] = fields.exclude if fields and fields.exclude else set()
631
842
 
@@ -1,5 +1,11 @@
1
1
  """elasticsearch extensions modifications."""
2
2
 
3
+ from .collections_search import CollectionsSearchEndpointExtension
3
4
  from .query import Operator, QueryableTypes, QueryExtension
4
5
 
5
- __all__ = ["Operator", "QueryableTypes", "QueryExtension"]
6
+ __all__ = [
7
+ "Operator",
8
+ "QueryableTypes",
9
+ "QueryExtension",
10
+ "CollectionsSearchEndpointExtension",
11
+ ]
@@ -0,0 +1,194 @@
1
+ """Collections search extension."""
2
+
3
+ from typing import List, Optional, Type, Union
4
+
5
+ from fastapi import APIRouter, FastAPI, Request
6
+ from fastapi.responses import JSONResponse
7
+ from pydantic import BaseModel
8
+ from stac_pydantic.api.search import ExtendedSearch
9
+ from starlette.responses import Response
10
+
11
+ from stac_fastapi.api.models import APIRequest
12
+ from stac_fastapi.types.core import BaseCoreClient
13
+ from stac_fastapi.types.extension import ApiExtension
14
+ from stac_fastapi.types.stac import Collections
15
+
16
+
17
+ class CollectionsSearchRequest(ExtendedSearch):
18
+ """Extended search model for collections with free text search support."""
19
+
20
+ q: Optional[Union[str, List[str]]] = None
21
+ token: Optional[str] = None
22
+ query: Optional[
23
+ str
24
+ ] = None # Legacy query extension (deprecated but still supported)
25
+
26
+
27
+ class CollectionsSearchEndpointExtension(ApiExtension):
28
+ """Collections search endpoint extension.
29
+
30
+ This extension adds a dedicated /collections-search endpoint for collection search operations.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ client: Optional[BaseCoreClient] = None,
36
+ settings: dict = None,
37
+ GET: Optional[Type[Union[BaseModel, APIRequest]]] = None,
38
+ POST: Optional[Type[Union[BaseModel, APIRequest]]] = None,
39
+ conformance_classes: Optional[List[str]] = None,
40
+ ):
41
+ """Initialize the extension.
42
+
43
+ Args:
44
+ client: Optional BaseCoreClient instance to use for this extension.
45
+ settings: Dictionary of settings to pass to the extension.
46
+ GET: Optional GET request model.
47
+ POST: Optional POST request model.
48
+ conformance_classes: Optional list of conformance classes to add to the API.
49
+ """
50
+ super().__init__()
51
+ self.client = client
52
+ self.settings = settings or {}
53
+ self.GET = GET
54
+ self.POST = POST
55
+ self.conformance_classes = conformance_classes or []
56
+ self.router = APIRouter()
57
+ self.create_endpoints()
58
+
59
+ def register(self, app: FastAPI) -> None:
60
+ """Register the extension with a FastAPI application.
61
+
62
+ Args:
63
+ app: target FastAPI application.
64
+
65
+ Returns:
66
+ None
67
+ """
68
+ app.include_router(self.router)
69
+
70
+ def create_endpoints(self) -> None:
71
+ """Create endpoints for the extension."""
72
+ if self.GET:
73
+ self.router.add_api_route(
74
+ name="Get Collections Search",
75
+ path="/collections-search",
76
+ response_model=None,
77
+ response_class=JSONResponse,
78
+ methods=["GET"],
79
+ endpoint=self.collections_search_get_endpoint,
80
+ **(self.settings if isinstance(self.settings, dict) else {}),
81
+ )
82
+
83
+ if self.POST:
84
+ self.router.add_api_route(
85
+ name="Post Collections Search",
86
+ path="/collections-search",
87
+ response_model=None,
88
+ response_class=JSONResponse,
89
+ methods=["POST"],
90
+ endpoint=self.collections_search_post_endpoint,
91
+ **(self.settings if isinstance(self.settings, dict) else {}),
92
+ )
93
+
94
+ async def collections_search_get_endpoint(
95
+ self, request: Request
96
+ ) -> Union[Collections, Response]:
97
+ """GET /collections-search endpoint.
98
+
99
+ Args:
100
+ request: Request object.
101
+
102
+ Returns:
103
+ Collections: Collections object.
104
+ """
105
+ # Extract query parameters from the request
106
+ params = dict(request.query_params)
107
+
108
+ # Convert query parameters to appropriate types
109
+ if "limit" in params:
110
+ try:
111
+ params["limit"] = int(params["limit"])
112
+ except ValueError:
113
+ pass
114
+
115
+ # Handle fields parameter
116
+ if "fields" in params:
117
+ fields_str = params.pop("fields")
118
+ fields = fields_str.split(",")
119
+ params["fields"] = fields
120
+
121
+ # Handle sortby parameter
122
+ if "sortby" in params:
123
+ sortby_str = params.pop("sortby")
124
+ sortby = sortby_str.split(",")
125
+ params["sortby"] = sortby
126
+
127
+ collections = await self.client.all_collections(request=request, **params)
128
+ return collections
129
+
130
+ async def collections_search_post_endpoint(
131
+ self, request: Request, body: dict
132
+ ) -> Union[Collections, Response]:
133
+ """POST /collections-search endpoint.
134
+
135
+ Args:
136
+ request: Request object.
137
+ body: Search request body.
138
+
139
+ Returns:
140
+ Collections: Collections object.
141
+ """
142
+ # Convert the dict to an ExtendedSearch model
143
+ search_request = CollectionsSearchRequest.model_validate(body)
144
+
145
+ # Check if fields are present in the body
146
+ if "fields" in body:
147
+ # Extract fields from body and add them to search_request
148
+ if hasattr(search_request, "field"):
149
+ from stac_pydantic.api.extensions.fields import FieldsExtension
150
+
151
+ fields_data = body["fields"]
152
+ search_request.field = FieldsExtension(
153
+ includes=fields_data.get("include"),
154
+ excludes=fields_data.get("exclude"),
155
+ )
156
+
157
+ # Set the postbody on the request for pagination links
158
+ request.postbody = body
159
+
160
+ collections = await self.client.post_all_collections(
161
+ search_request=search_request, request=request
162
+ )
163
+
164
+ return collections
165
+
166
+ @classmethod
167
+ def from_extensions(
168
+ cls, extensions: List[ApiExtension]
169
+ ) -> "CollectionsSearchEndpointExtension":
170
+ """Create a CollectionsSearchEndpointExtension from a list of extensions.
171
+
172
+ Args:
173
+ extensions: List of extensions to include in the CollectionsSearchEndpointExtension.
174
+
175
+ Returns:
176
+ CollectionsSearchEndpointExtension: A new CollectionsSearchEndpointExtension instance.
177
+ """
178
+ from stac_fastapi.api.models import (
179
+ create_get_request_model,
180
+ create_post_request_model,
181
+ )
182
+
183
+ get_model = create_get_request_model(extensions)
184
+ post_model = create_post_request_model(extensions)
185
+
186
+ return cls(
187
+ GET=get_model,
188
+ POST=post_model,
189
+ conformance_classes=[
190
+ ext.conformance_classes
191
+ for ext in extensions
192
+ if hasattr(ext, "conformance_classes")
193
+ ],
194
+ )
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "6.4.0"
2
+ __version__ = "6.5.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi-core
3
- Version: 6.4.0
3
+ Version: 6.5.1
4
4
  Summary: Core library for the Elasticsearch and Opensearch stac-fastapi backends.
5
5
  Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -97,13 +97,13 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
97
97
  ## Table of Contents
98
98
 
99
99
  - [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
100
- - [Sponsors \& Supporters](#sponsors--supporters)
100
+ - [Sponsors & Supporters](#sponsors--supporters)
101
101
  - [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
102
102
  - [Common Deployment Patterns](#common-deployment-patterns)
103
103
  - [Technologies](#technologies)
104
104
  - [Table of Contents](#table-of-contents)
105
105
  - [Collection Search Extensions](#collection-search-extensions)
106
- - [Documentation \& Resources](#documentation--resources)
106
+ - [Documentation & Resources](#documentation--resources)
107
107
  - [Package Structure](#package-structure)
108
108
  - [Examples](#examples)
109
109
  - [Performance](#performance)
@@ -146,7 +146,11 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
146
146
 
147
147
  ## Collection Search Extensions
148
148
 
149
- SFEOS implements extended capabilities for the `/collections` endpoint, allowing for more powerful collection discovery:
149
+ SFEOS provides enhanced collection search capabilities through two primary routes:
150
+ - **GET/POST `/collections`**: The standard STAC endpoint with extended query parameters
151
+ - **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)
152
+
153
+ These endpoints support advanced collection discovery features including:
150
154
 
151
155
  - **Sorting**: Sort collections by sortable fields using the `sortby` parameter
152
156
  - Example: `/collections?sortby=+id` (ascending sort by ID)
@@ -162,9 +166,26 @@ SFEOS implements extended capabilities for the `/collections` endpoint, allowing
162
166
  - Searches across multiple text fields including title, description, and keywords
163
167
  - Supports partial word matching and relevance-based sorting
164
168
 
169
+ - **Structured Filtering**: Filter collections using CQL2 expressions
170
+ - JSON format: `/collections?filter={"op":"=","args":[{"property":"id"},"sentinel-2"]}&filter-lang=cql2-json`
171
+ - Text format: `/collections?filter=id='sentinel-2'&filter-lang=cql2-text` (note: string values must be quoted)
172
+ - Advanced text format: `/collections?filter=id LIKE '%sentinel%'&filter-lang=cql2-text` (supports LIKE, BETWEEN, etc.)
173
+ - Supports both CQL2 JSON and CQL2 text formats with various operators
174
+ - Enables precise filtering on any collection property
175
+
176
+ - **Datetime Filtering**: Filter collections by their temporal extent using the `datetime` parameter
177
+ - Example: `/collections?datetime=2020-01-01T00:00:00Z/2020-12-31T23:59:59Z` (finds collections with temporal extents that overlap this range)
178
+ - Example: `/collections?datetime=2020-06-15T12:00:00Z` (finds collections whose temporal extent includes this specific time)
179
+ - Example: `/collections?datetime=2020-01-01T00:00:00Z/..` (finds collections with temporal extents that extend to or beyond January 1, 2020)
180
+ - Example: `/collections?datetime=../2020-12-31T23:59:59Z` (finds collections with temporal extents that begin on or before December 31, 2020)
181
+ - Collections are matched if their temporal extent overlaps with the provided datetime parameter
182
+ - This allows for efficient discovery of collections based on time periods
183
+
165
184
  These extensions make it easier to build user interfaces that display and navigate through collections efficiently.
166
185
 
167
- > **Configuration**: Collection search extensions can be disabled by setting the `ENABLE_COLLECTIONS_SEARCH` environment variable to `false`. By default, these extensions are enabled.
186
+ > **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.
187
+ >
188
+ > **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**.
168
189
 
169
190
  > **Note**: Sorting is only available on fields that are indexed for sorting in Elasticsearch/OpenSearch. With the default mappings, you can sort on:
170
191
  > - `id` (keyword field)
@@ -175,6 +196,7 @@ These extensions make it easier to build user interfaces that display and naviga
175
196
  >
176
197
  > **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.
177
198
 
199
+
178
200
  ## Package Structure
179
201
 
180
202
  This project is organized into several packages, each with a specific purpose:
@@ -187,7 +209,7 @@ This project is organized into several packages, each with a specific purpose:
187
209
  - Shared logic and utilities that improve code reuse between backends
188
210
 
189
211
  - **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`.
190
- -
212
+
191
213
  - **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`.
192
214
 
193
215
  ## Examples
@@ -305,12 +327,13 @@ You can customize additional settings in your `.env` file:
305
327
  | `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
306
328
  | `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 |
307
329
  | `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 |
308
- | `ENABLE_COLLECTIONS_SEARCH` | Enable collection search extensions (sort, fields). | `true` | Optional |
309
- | `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 |
330
+ | `ENABLE_COLLECTIONS_SEARCH` | Enable collection search extensions (sort, fields, free text search, structured filtering, and datetime filtering) on the core `/collections` endpoint. | `true` | Optional |
331
+ | `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 |
332
+ | `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 |
310
333
  | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
311
334
  | `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 |
312
335
  | `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 |
313
- | `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 |
336
+ | `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 |
314
337
 
315
338
  > [!NOTE]
316
339
  > 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.
@@ -456,7 +479,6 @@ The system uses a precise naming convention:
456
479
  - `ENABLE_COLLECTIONS_SEARCH`: Set to `true` (default) to enable collection search extensions (sort, fields). Set to `false` to disable.
457
480
  - `ENABLE_TRANSACTIONS_EXTENSIONS`: Set to `true` (default) to enable transaction extensions. Set to `false` to disable.
458
481
 
459
-
460
482
  ## Collection Pagination
461
483
 
462
484
  - **Overview**: The collections route supports pagination through optional query parameters.
@@ -2,23 +2,24 @@ stac_fastapi/core/__init__.py,sha256=8izV3IWRGdXmDOK1hIPQAanbWs9EI04PJCGgqG1ZGIs
2
2
  stac_fastapi/core/base_database_logic.py,sha256=nhj0CZNur_SRs4GtXTER-Zjq8JPub5zINiCKbCjw0Bs,3814
3
3
  stac_fastapi/core/base_settings.py,sha256=R3_Sx7n5XpGMs3zAwFJD7y008WvGU_uI2xkaabm82Kg,239
4
4
  stac_fastapi/core/basic_auth.py,sha256=RhFv3RVSHF6OaqnaaU2DO4ncJ_S5nB1q8UNpnVJJsrk,2155
5
- stac_fastapi/core/core.py,sha256=THx8G60pFwaORGietCbNLPTPl04tDllnmVgYqJ6zVtU,39336
5
+ stac_fastapi/core/core.py,sha256=D_TUtcPnLKWDLnY-oPw5jsmqoA2ghLqPPs6H56c4myc,48369
6
6
  stac_fastapi/core/datetime_utils.py,sha256=TrTgbU7AKNC-ic4a3HptfE5XAc9tHR7uJasZyhOuwnc,2633
7
7
  stac_fastapi/core/rate_limit.py,sha256=Gu8dAaJReGsj1L91U6m2tflU6RahpXDRs2-AYSKoybA,1318
8
8
  stac_fastapi/core/route_dependencies.py,sha256=hdtuMkv-zY1vg0YxiCz1aKP0SbBcORqDGEKDGgEazW8,5482
9
9
  stac_fastapi/core/serializers.py,sha256=HU7sVSMa6w_F_qs_gdAeIFZ18GW-6t8ZHFmgI4-1uNw,7455
10
10
  stac_fastapi/core/session.py,sha256=aXqu4LXfVbAAsChMVXd9gAhczA2bZPne6HqPeklAwMY,474
11
11
  stac_fastapi/core/utilities.py,sha256=WbspaJey_Cs-7TrBKasdqq7yjB7vjKiU01KyJM0m8_E,7506
12
- stac_fastapi/core/version.py,sha256=1xfRb7s1Stv055oEewNJpZn9OQUUcaJvgSzCsYLHYSQ,45
13
- stac_fastapi/core/extensions/__init__.py,sha256=2MCo0UoInkgItIM8id-rbeygzn_qUOvTGfr8jFXZjHQ,167
12
+ stac_fastapi/core/version.py,sha256=FuGC3fKnAmD4Wk95swJ6qCVBs5mZiShrlRKuSH-voyE,45
13
+ stac_fastapi/core/extensions/__init__.py,sha256=zSIAqou8jnakWPbkh4Ddcx1-oazZVBOs7U2PAakAdU0,291
14
14
  stac_fastapi/core/extensions/aggregation.py,sha256=v1hUHqlYuMqfQ554g3cTp16pUyRYucQxPERbHPAFtf8,1878
15
+ stac_fastapi/core/extensions/collections_search.py,sha256=q7eRBykEqNRCiTfkmM_TobqKkxA3n1zQ7dYo37juE6s,6503
15
16
  stac_fastapi/core/extensions/fields.py,sha256=NCT5XHvfaf297eDPNaIFsIzvJnbbUTpScqF0otdx0NA,1066
16
17
  stac_fastapi/core/extensions/filter.py,sha256=-NQGME7rR_ereuDx-LAa1M5JhEXFaKiTtkH2asraYHE,2998
17
18
  stac_fastapi/core/extensions/query.py,sha256=Xmo8pfZEZKPudZEjjozv3R0wLOP0ayjC9E67sBOXqWY,1803
18
19
  stac_fastapi/core/models/__init__.py,sha256=g-D1DiGfmC9Bg27DW9JzkN6fAvscv75wyhyiZ6NzvIk,48
19
20
  stac_fastapi/core/models/links.py,sha256=0dWSEMt3aa7NCISlHwo11zLBeIV1LwXG3JGjrXC3dZI,6672
20
21
  stac_fastapi/core/models/search.py,sha256=7SgAUyzHGXBXSqB4G6cwq9FMwoAS00momb7jvBkjyow,27
21
- stac_fastapi_core-6.4.0.dist-info/METADATA,sha256=EpPyfAj7liMxtW-_OrgONVEYi6vk7d7cmQgcMdNWSVk,39348
22
- stac_fastapi_core-6.4.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
23
- stac_fastapi_core-6.4.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
24
- stac_fastapi_core-6.4.0.dist-info/RECORD,,
22
+ stac_fastapi_core-6.5.1.dist-info/METADATA,sha256=PIBVPUY1dqhKMj_JDMOUYuyLoSa8mDoLuUpfI_sjKcU,41746
23
+ stac_fastapi_core-6.5.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
24
+ stac_fastapi_core-6.5.1.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
25
+ stac_fastapi_core-6.5.1.dist-info/RECORD,,