stac-fastapi-core 6.2.1__tar.gz → 6.3.0__tar.gz

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.
Files changed (30) hide show
  1. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/PKG-INFO +41 -43
  2. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/README.md +39 -20
  3. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/core.py +81 -77
  4. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/datetime_utils.py +4 -1
  5. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/serializers.py +35 -1
  6. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/utilities.py +9 -1
  7. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/version.py +1 -1
  8. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi_core.egg-info/PKG-INFO +42 -44
  9. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi_core.egg-info/top_level.txt +0 -1
  10. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/setup.cfg +0 -0
  11. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/setup.py +0 -0
  12. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/__init__.py +0 -0
  13. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/base_database_logic.py +0 -0
  14. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/base_settings.py +0 -0
  15. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/basic_auth.py +0 -0
  16. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/extensions/__init__.py +0 -0
  17. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/extensions/aggregation.py +0 -0
  18. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/extensions/fields.py +0 -0
  19. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/extensions/filter.py +0 -0
  20. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/extensions/query.py +0 -0
  21. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/models/__init__.py +0 -0
  22. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/models/links.py +0 -0
  23. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/models/search.py +0 -0
  24. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/rate_limit.py +0 -0
  25. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/route_dependencies.py +0 -0
  26. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi/core/session.py +0 -0
  27. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi_core.egg-info/SOURCES.txt +0 -0
  28. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi_core.egg-info/dependency_links.txt +0 -0
  29. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi_core.egg-info/not-zip-safe +0 -0
  30. {stac_fastapi_core-6.2.1 → stac_fastapi_core-6.3.0}/stac_fastapi_core.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: stac_fastapi_core
3
- Version: 6.2.1
3
+ Version: 6.3.0
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
@@ -15,27 +15,6 @@ 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: fastapi~=0.109.0
19
- Requires-Dist: attrs>=23.2.0
20
- Requires-Dist: pydantic<3.0.0,>=2.4.1
21
- Requires-Dist: stac_pydantic~=3.3.0
22
- Requires-Dist: stac-fastapi.types==6.0.0
23
- Requires-Dist: stac-fastapi.api==6.0.0
24
- Requires-Dist: stac-fastapi.extensions==6.0.0
25
- Requires-Dist: orjson~=3.9.0
26
- Requires-Dist: overrides~=7.4.0
27
- Requires-Dist: geojson-pydantic~=1.0.0
28
- Requires-Dist: pygeofilter~=0.3.1
29
- Requires-Dist: jsonschema~=4.0.0
30
- Requires-Dist: slowapi~=0.1.9
31
- Dynamic: classifier
32
- Dynamic: description
33
- Dynamic: description-content-type
34
- Dynamic: home-page
35
- Dynamic: license
36
- Dynamic: requires-dist
37
- Dynamic: requires-python
38
- Dynamic: summary
39
18
 
40
19
  # stac-fastapi-elasticsearch-opensearch
41
20
 
@@ -105,26 +84,43 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
105
84
 
106
85
  ## Table of Contents
107
86
 
108
- - [Documentation & Resources](#documentation--resources)
109
- - [Package Structure](#package-structure)
110
- - [Examples](#examples)
111
- - [Performance](#performance)
112
- - [Quick Start](#quick-start)
113
- - [Installation](#installation)
114
- - [Running Locally](#running-locally)
115
- - [Configuration reference](#configuration-reference)
116
- - [Interacting with the API](#interacting-with-the-api)
117
- - [Configure the API](#configure-the-api)
118
- - [Collection pagination](#collection-pagination)
119
- - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
120
- - [Elasticsearch Mappings](#elasticsearch-mappings)
121
- - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
122
- - [Snapshots](#snapshots)
123
- - [Reindexing](#reindexing)
124
- - [Auth](#auth)
125
- - [Aggregation](#aggregation)
126
- - [Rate Limiting](#rate-limiting)
127
- - [Datetime-Based Index Management](#datetime-based-index-management)
87
+ - [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
88
+ - [Sponsors \& Supporters](#sponsors--supporters)
89
+ - [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
90
+ - [Common Deployment Patterns](#common-deployment-patterns)
91
+ - [Technologies](#technologies)
92
+ - [Table of Contents](#table-of-contents)
93
+ - [Documentation \& Resources](#documentation--resources)
94
+ - [Package Structure](#package-structure)
95
+ - [Examples](#examples)
96
+ - [Performance](#performance)
97
+ - [Direct Response Mode](#direct-response-mode)
98
+ - [Quick Start](#quick-start)
99
+ - [Installation](#installation)
100
+ - [Running Locally](#running-locally)
101
+ - [Using Pre-built Docker Images](#using-pre-built-docker-images)
102
+ - [Using Docker Compose](#using-docker-compose)
103
+ - [Configuration Reference](#configuration-reference)
104
+ - [Datetime-Based Index Management](#datetime-based-index-management)
105
+ - [Overview](#overview)
106
+ - [When to Use](#when-to-use)
107
+ - [Configuration](#configuration)
108
+ - [Enabling Datetime-Based Indexing](#enabling-datetime-based-indexing)
109
+ - [Related Configuration Variables](#related-configuration-variables)
110
+ - [How Datetime-Based Indexing Works](#how-datetime-based-indexing-works)
111
+ - [Index and Alias Naming Convention](#index-and-alias-naming-convention)
112
+ - [Index Size Management](#index-size-management)
113
+ - [Interacting with the API](#interacting-with-the-api)
114
+ - [Configure the API](#configure-the-api)
115
+ - [Collection Pagination](#collection-pagination)
116
+ - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
117
+ - [Elasticsearch Mappings](#elasticsearch-mappings)
118
+ - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
119
+ - [Snapshots](#snapshots)
120
+ - [Reindexing](#reindexing)
121
+ - [Auth](#auth)
122
+ - [Aggregation](#aggregation)
123
+ - [Rate Limiting](#rate-limiting)
128
124
 
129
125
  ## Documentation & Resources
130
126
 
@@ -267,6 +263,8 @@ You can customize additional settings in your `.env` file:
267
263
  | `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 |
268
264
  | `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 |
269
265
  | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
266
+ | `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 |
267
+ | `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 |
270
268
 
271
269
  > [!NOTE]
272
270
  > 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.
@@ -66,26 +66,43 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
66
66
 
67
67
  ## Table of Contents
68
68
 
69
- - [Documentation & Resources](#documentation--resources)
70
- - [Package Structure](#package-structure)
71
- - [Examples](#examples)
72
- - [Performance](#performance)
73
- - [Quick Start](#quick-start)
74
- - [Installation](#installation)
75
- - [Running Locally](#running-locally)
76
- - [Configuration reference](#configuration-reference)
77
- - [Interacting with the API](#interacting-with-the-api)
78
- - [Configure the API](#configure-the-api)
79
- - [Collection pagination](#collection-pagination)
80
- - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
81
- - [Elasticsearch Mappings](#elasticsearch-mappings)
82
- - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
83
- - [Snapshots](#snapshots)
84
- - [Reindexing](#reindexing)
85
- - [Auth](#auth)
86
- - [Aggregation](#aggregation)
87
- - [Rate Limiting](#rate-limiting)
88
- - [Datetime-Based Index Management](#datetime-based-index-management)
69
+ - [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
70
+ - [Sponsors \& Supporters](#sponsors--supporters)
71
+ - [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
72
+ - [Common Deployment Patterns](#common-deployment-patterns)
73
+ - [Technologies](#technologies)
74
+ - [Table of Contents](#table-of-contents)
75
+ - [Documentation \& Resources](#documentation--resources)
76
+ - [Package Structure](#package-structure)
77
+ - [Examples](#examples)
78
+ - [Performance](#performance)
79
+ - [Direct Response Mode](#direct-response-mode)
80
+ - [Quick Start](#quick-start)
81
+ - [Installation](#installation)
82
+ - [Running Locally](#running-locally)
83
+ - [Using Pre-built Docker Images](#using-pre-built-docker-images)
84
+ - [Using Docker Compose](#using-docker-compose)
85
+ - [Configuration Reference](#configuration-reference)
86
+ - [Datetime-Based Index Management](#datetime-based-index-management)
87
+ - [Overview](#overview)
88
+ - [When to Use](#when-to-use)
89
+ - [Configuration](#configuration)
90
+ - [Enabling Datetime-Based Indexing](#enabling-datetime-based-indexing)
91
+ - [Related Configuration Variables](#related-configuration-variables)
92
+ - [How Datetime-Based Indexing Works](#how-datetime-based-indexing-works)
93
+ - [Index and Alias Naming Convention](#index-and-alias-naming-convention)
94
+ - [Index Size Management](#index-size-management)
95
+ - [Interacting with the API](#interacting-with-the-api)
96
+ - [Configure the API](#configure-the-api)
97
+ - [Collection Pagination](#collection-pagination)
98
+ - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
99
+ - [Elasticsearch Mappings](#elasticsearch-mappings)
100
+ - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
101
+ - [Snapshots](#snapshots)
102
+ - [Reindexing](#reindexing)
103
+ - [Auth](#auth)
104
+ - [Aggregation](#aggregation)
105
+ - [Rate Limiting](#rate-limiting)
89
106
 
90
107
  ## Documentation & Resources
91
108
 
@@ -228,6 +245,8 @@ You can customize additional settings in your `.env` file:
228
245
  | `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 |
229
246
  | `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 |
230
247
  | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
248
+ | `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 |
249
+ | `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 |
231
250
 
232
251
  > [!NOTE]
233
252
  > 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.
@@ -284,86 +284,63 @@ class CoreClient(AsyncBaseCoreClient):
284
284
  async def item_collection(
285
285
  self,
286
286
  collection_id: str,
287
+ request: Request,
287
288
  bbox: Optional[BBox] = None,
288
289
  datetime: Optional[str] = None,
289
290
  limit: Optional[int] = None,
291
+ sortby: Optional[str] = None,
292
+ filter_expr: Optional[str] = None,
293
+ filter_lang: Optional[str] = None,
290
294
  token: Optional[str] = None,
295
+ query: Optional[str] = None,
296
+ fields: Optional[List[str]] = None,
291
297
  **kwargs,
292
298
  ) -> stac_types.ItemCollection:
293
- """Read items from a specific collection in the database.
299
+ """List items within a specific collection.
300
+
301
+ This endpoint delegates to ``get_search`` under the hood with
302
+ ``collections=[collection_id]`` so that filtering, sorting and pagination
303
+ behave identically to the Search endpoints.
294
304
 
295
305
  Args:
296
- collection_id (str): The identifier of the collection to read items from.
297
- bbox (Optional[BBox]): The bounding box to filter items by.
298
- datetime (Optional[str]): The datetime range to filter items by.
299
- limit (int): The maximum number of items to return.
300
- token (str): A token used for pagination.
301
- request (Request): The incoming request.
306
+ collection_id (str): ID of the collection to list items from.
307
+ request (Request): FastAPI Request object.
308
+ bbox (Optional[BBox]): Optional bounding box filter.
309
+ datetime (Optional[str]): Optional datetime or interval filter.
310
+ limit (Optional[int]): Optional page size. Defaults to env ``STAC_ITEM_LIMIT`` when unset.
311
+ sortby (Optional[str]): Optional sort specification. Accepts repeated values
312
+ like ``sortby=-properties.datetime`` or ``sortby=+id``. Bare fields (e.g. ``sortby=id``)
313
+ imply ascending order.
314
+ token (Optional[str]): Optional pagination token.
315
+ query (Optional[str]): Optional query string.
316
+ filter_expr (Optional[str]): Optional filter expression.
317
+ filter_lang (Optional[str]): Optional filter language.
318
+ fields (Optional[List[str]]): Fields to include or exclude from the results.
302
319
 
303
320
  Returns:
304
- ItemCollection: An `ItemCollection` object containing the items from the specified collection that meet
305
- the filter criteria and links to various resources.
321
+ ItemCollection: Feature collection with items, paging links, and counts.
306
322
 
307
323
  Raises:
308
- HTTPException: If the specified collection is not found.
309
- Exception: If any error occurs while reading the items from the database.
324
+ HTTPException: 404 if the collection does not exist.
310
325
  """
311
- request: Request = kwargs["request"]
312
- token = request.query_params.get("token")
313
-
314
- base_url = str(request.base_url)
315
-
316
- collection = await self.get_collection(
317
- collection_id=collection_id, request=request
318
- )
319
- collection_id = collection.get("id")
320
- if collection_id is None:
321
- raise HTTPException(status_code=404, detail="Collection not found")
322
-
323
- search = self.database.make_search()
324
- search = self.database.apply_collections_filter(
325
- search=search, collection_ids=[collection_id]
326
- )
327
-
328
326
  try:
329
- search, datetime_search = self.database.apply_datetime_filter(
330
- search=search, datetime=datetime
331
- )
332
- except (ValueError, TypeError) as e:
333
- # Handle invalid interval formats if return_date fails
334
- msg = f"Invalid interval format: {datetime}, error: {e}"
335
- logger.error(msg)
336
- raise HTTPException(status_code=400, detail=msg)
337
-
338
- if bbox:
339
- bbox = [float(x) for x in bbox]
340
- if len(bbox) == 6:
341
- bbox = [bbox[0], bbox[1], bbox[3], bbox[4]]
342
-
343
- search = self.database.apply_bbox_filter(search=search, bbox=bbox)
327
+ await self.get_collection(collection_id=collection_id, request=request)
328
+ except Exception:
329
+ raise HTTPException(status_code=404, detail="Collection not found")
344
330
 
345
- limit = int(request.query_params.get("limit", os.getenv("STAC_ITEM_LIMIT", 10)))
346
- items, maybe_count, next_token = await self.database.execute_search(
347
- search=search,
331
+ # Delegate directly to GET search for consistency
332
+ return await self.get_search(
333
+ request=request,
334
+ collections=[collection_id],
335
+ bbox=bbox,
336
+ datetime=datetime,
348
337
  limit=limit,
349
- sort=None,
350
338
  token=token,
351
- collection_ids=[collection_id],
352
- datetime_search=datetime_search,
353
- )
354
-
355
- items = [
356
- self.item_serializer.db_to_stac(item, base_url=base_url) for item in items
357
- ]
358
-
359
- links = await PagingLinks(request=request, next=next_token).get_links()
360
-
361
- return stac_types.ItemCollection(
362
- type="FeatureCollection",
363
- features=items,
364
- links=links,
365
- numReturned=len(items),
366
- numMatched=maybe_count,
339
+ sortby=sortby,
340
+ query=query,
341
+ filter_expr=filter_expr,
342
+ filter_lang=filter_lang,
343
+ fields=fields,
367
344
  )
368
345
 
369
346
  async def get_item(
@@ -429,6 +406,7 @@ class CoreClient(AsyncBaseCoreClient):
429
406
  HTTPException: If any error occurs while searching the catalog.
430
407
  """
431
408
  limit = int(request.query_params.get("limit", os.getenv("STAC_ITEM_LIMIT", 10)))
409
+
432
410
  base_args = {
433
411
  "collections": collections,
434
412
  "ids": ids,
@@ -446,10 +424,18 @@ class CoreClient(AsyncBaseCoreClient):
446
424
  base_args["intersects"] = orjson.loads(unquote_plus(intersects))
447
425
 
448
426
  if sortby:
449
- base_args["sortby"] = [
450
- {"field": sort[1:], "direction": "desc" if sort[0] == "-" else "asc"}
451
- for sort in sortby
452
- ]
427
+ parsed_sort = []
428
+ for raw in sortby:
429
+ if not isinstance(raw, str):
430
+ continue
431
+ s = raw.strip()
432
+ if not s:
433
+ continue
434
+ direction = "desc" if s[0] == "-" else "asc"
435
+ field = s[1:] if s and s[0] in "+-" else s
436
+ parsed_sort.append({"field": field, "direction": direction})
437
+ if parsed_sort:
438
+ base_args["sortby"] = parsed_sort
453
439
 
454
440
  if filter_expr:
455
441
  base_args["filter_lang"] = "cql2-json"
@@ -526,13 +512,15 @@ class CoreClient(AsyncBaseCoreClient):
526
512
 
527
513
  search = self.database.apply_bbox_filter(search=search, bbox=bbox)
528
514
 
529
- if search_request.intersects:
515
+ if hasattr(search_request, "intersects") and getattr(
516
+ search_request, "intersects"
517
+ ):
530
518
  search = self.database.apply_intersects_filter(
531
- search=search, intersects=search_request.intersects
519
+ search=search, intersects=getattr(search_request, "intersects")
532
520
  )
533
521
 
534
- if search_request.query:
535
- for field_name, expr in search_request.query.items():
522
+ if hasattr(search_request, "query") and getattr(search_request, "query"):
523
+ for field_name, expr in getattr(search_request, "query").items():
536
524
  field = "properties__" + field_name
537
525
  for op, value in expr.items():
538
526
  # Convert enum to string
@@ -541,9 +529,14 @@ class CoreClient(AsyncBaseCoreClient):
541
529
  search=search, op=operator, field=field, value=value
542
530
  )
543
531
 
544
- # only cql2_json is supported here
532
+ # Apply CQL2 filter (support both 'filter_expr' and canonical 'filter')
533
+ cql2_filter = None
545
534
  if hasattr(search_request, "filter_expr"):
546
535
  cql2_filter = getattr(search_request, "filter_expr", None)
536
+ if cql2_filter is None and hasattr(search_request, "filter"):
537
+ cql2_filter = getattr(search_request, "filter", None)
538
+
539
+ if cql2_filter is not None:
547
540
  try:
548
541
  search = await self.database.apply_cql2_filter(search, cql2_filter)
549
542
  except Exception as e:
@@ -561,19 +554,23 @@ class CoreClient(AsyncBaseCoreClient):
561
554
  )
562
555
 
563
556
  sort = None
564
- if search_request.sortby:
565
- sort = self.database.populate_sort(search_request.sortby)
557
+ if hasattr(search_request, "sortby") and getattr(search_request, "sortby"):
558
+ sort = self.database.populate_sort(getattr(search_request, "sortby"))
566
559
 
567
560
  limit = 10
568
561
  if search_request.limit:
569
562
  limit = search_request.limit
570
563
 
564
+ # Use token from the request if the model doesn't define it
565
+ token_param = getattr(
566
+ search_request, "token", None
567
+ ) or request.query_params.get("token")
571
568
  items, maybe_count, next_token = await self.database.execute_search(
572
569
  search=search,
573
570
  limit=limit,
574
- token=search_request.token,
571
+ token=token_param,
575
572
  sort=sort,
576
- collection_ids=search_request.collections,
573
+ collection_ids=getattr(search_request, "collections", None),
577
574
  datetime_search=datetime_search,
578
575
  )
579
576
 
@@ -917,7 +914,7 @@ class TransactionsClient(AsyncBaseTransactionsClient):
917
914
 
918
915
  @attr.s
919
916
  class BulkTransactionsClient(BaseBulkTransactionsClient):
920
- """A client for posting bulk transactions to a Postgres database.
917
+ """A client for posting bulk transactions.
921
918
 
922
919
  Attributes:
923
920
  session: An instance of `Session` to use for database connection.
@@ -965,6 +962,13 @@ class BulkTransactionsClient(BaseBulkTransactionsClient):
965
962
  A string indicating the number of items successfully added.
966
963
  """
967
964
  request = kwargs.get("request")
965
+
966
+ if os.getenv("ENABLE_DATETIME_INDEX_FILTERING"):
967
+ raise HTTPException(
968
+ status_code=400,
969
+ detail="The /collections/{collection_id}/bulk_items endpoint is invalid when ENABLE_DATETIME_INDEX_FILTERING is set to true. Try using the /collections/{collection_id}/items endpoint.",
970
+ )
971
+
968
972
  if request:
969
973
  base_url = str(request.base_url)
970
974
  else:
@@ -17,17 +17,20 @@ def format_datetime_range(date_str: str) -> str:
17
17
  """
18
18
 
19
19
  def normalize(dt):
20
+ """Normalize datetime string and preserve millisecond precision."""
20
21
  dt = dt.strip()
21
22
  if not dt or dt == "..":
22
23
  return ".."
23
24
  dt_obj = rfc3339_str_to_datetime(dt)
24
25
  dt_utc = dt_obj.astimezone(timezone.utc)
25
- return dt_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
26
+ return dt_utc.isoformat(timespec="milliseconds").replace("+00:00", "Z")
26
27
 
27
28
  if not isinstance(date_str, str):
28
29
  return "../.."
30
+
29
31
  if "/" not in date_str:
30
32
  return f"{normalize(date_str)}/{normalize(date_str)}"
33
+
31
34
  try:
32
35
  start, end = date_str.split("/", 1)
33
36
  except Exception:
@@ -9,6 +9,7 @@ from starlette.requests import Request
9
9
 
10
10
  from stac_fastapi.core.datetime_utils import now_to_rfc3339_str
11
11
  from stac_fastapi.core.models.links import CollectionLinks
12
+ from stac_fastapi.core.utilities import get_bool_env
12
13
  from stac_fastapi.types import stac as stac_types
13
14
  from stac_fastapi.types.links import ItemLinks, resolve_links
14
15
 
@@ -66,6 +67,11 @@ class ItemSerializer(Serializer):
66
67
  item_links = resolve_links(stac_data.get("links", []), base_url)
67
68
  stac_data["links"] = item_links
68
69
 
70
+ if get_bool_env("STAC_INDEX_ASSETS"):
71
+ stac_data["assets"] = [
72
+ {"es_key": k, **v} for k, v in stac_data.get("assets", {}).items()
73
+ ]
74
+
69
75
  now = now_to_rfc3339_str()
70
76
  if "created" not in stac_data["properties"]:
71
77
  stac_data["properties"]["created"] = now
@@ -93,6 +99,12 @@ class ItemSerializer(Serializer):
93
99
  if original_links:
94
100
  item_links += resolve_links(original_links, base_url)
95
101
 
102
+ if get_bool_env("STAC_INDEX_ASSETS"):
103
+ assets = {a.pop("es_key"): a for a in item.get("assets", [])}
104
+
105
+ else:
106
+ assets = item.get("assets", {})
107
+
96
108
  return stac_types.Item(
97
109
  type="Feature",
98
110
  stac_version=item.get("stac_version", ""),
@@ -103,7 +115,7 @@ class ItemSerializer(Serializer):
103
115
  bbox=item.get("bbox", []),
104
116
  properties=item.get("properties", {}),
105
117
  links=item_links,
106
- assets=item.get("assets", {}),
118
+ assets=assets,
107
119
  )
108
120
 
109
121
 
@@ -128,6 +140,15 @@ class CollectionSerializer(Serializer):
128
140
  collection["links"] = resolve_links(
129
141
  collection.get("links", []), str(request.base_url)
130
142
  )
143
+
144
+ if get_bool_env("STAC_INDEX_ASSETS"):
145
+ collection["assets"] = [
146
+ {"es_key": k, **v} for k, v in collection.get("assets", {}).items()
147
+ ]
148
+ collection["item_assets"] = [
149
+ {"es_key": k, **v} for k, v in collection.get("item_assets", {}).items()
150
+ ]
151
+
131
152
  return collection
132
153
 
133
154
  @classmethod
@@ -174,5 +195,18 @@ class CollectionSerializer(Serializer):
174
195
  collection_links += resolve_links(original_links, str(request.base_url))
175
196
  collection["links"] = collection_links
176
197
 
198
+ if get_bool_env("STAC_INDEX_ASSETS"):
199
+ collection["assets"] = {
200
+ a.pop("es_key"): a for a in collection.get("assets", [])
201
+ }
202
+ collection["item_assets"] = {
203
+ i.pop("es_key"): i for i in collection.get("item_assets", [])
204
+ }
205
+
206
+ else:
207
+ collection["assets"] = collection.get("assets", {})
208
+ if item_assets := collection.get("item_assets"):
209
+ collection["item_assets"] = item_assets
210
+
177
211
  # Return the stac_types.Collection object
178
212
  return stac_types.Collection(**collection)
@@ -10,7 +10,15 @@ from typing import Any, Dict, List, Optional, Set, Union
10
10
 
11
11
  from stac_fastapi.types.stac import Item
12
12
 
13
- MAX_LIMIT = 10000
13
+
14
+ def get_max_limit():
15
+ """
16
+ Retrieve a MAX_LIMIT value from an environment variable.
17
+
18
+ Returns:
19
+ int: The int value parsed from the environment variable.
20
+ """
21
+ return int(os.getenv("ENV_MAX_LIMIT", 10000))
14
22
 
15
23
 
16
24
  def get_bool_env(name: str, default: Union[bool, str] = False) -> bool:
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "6.2.1"
2
+ __version__ = "6.3.0"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
2
- Name: stac_fastapi_core
3
- Version: 6.2.1
1
+ Metadata-Version: 2.1
2
+ Name: stac-fastapi-core
3
+ Version: 6.3.0
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
@@ -15,27 +15,6 @@ 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: fastapi~=0.109.0
19
- Requires-Dist: attrs>=23.2.0
20
- Requires-Dist: pydantic<3.0.0,>=2.4.1
21
- Requires-Dist: stac_pydantic~=3.3.0
22
- Requires-Dist: stac-fastapi.types==6.0.0
23
- Requires-Dist: stac-fastapi.api==6.0.0
24
- Requires-Dist: stac-fastapi.extensions==6.0.0
25
- Requires-Dist: orjson~=3.9.0
26
- Requires-Dist: overrides~=7.4.0
27
- Requires-Dist: geojson-pydantic~=1.0.0
28
- Requires-Dist: pygeofilter~=0.3.1
29
- Requires-Dist: jsonschema~=4.0.0
30
- Requires-Dist: slowapi~=0.1.9
31
- Dynamic: classifier
32
- Dynamic: description
33
- Dynamic: description-content-type
34
- Dynamic: home-page
35
- Dynamic: license
36
- Dynamic: requires-dist
37
- Dynamic: requires-python
38
- Dynamic: summary
39
18
 
40
19
  # stac-fastapi-elasticsearch-opensearch
41
20
 
@@ -105,26 +84,43 @@ This project is built on the following technologies: STAC, stac-fastapi, FastAPI
105
84
 
106
85
  ## Table of Contents
107
86
 
108
- - [Documentation & Resources](#documentation--resources)
109
- - [Package Structure](#package-structure)
110
- - [Examples](#examples)
111
- - [Performance](#performance)
112
- - [Quick Start](#quick-start)
113
- - [Installation](#installation)
114
- - [Running Locally](#running-locally)
115
- - [Configuration reference](#configuration-reference)
116
- - [Interacting with the API](#interacting-with-the-api)
117
- - [Configure the API](#configure-the-api)
118
- - [Collection pagination](#collection-pagination)
119
- - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
120
- - [Elasticsearch Mappings](#elasticsearch-mappings)
121
- - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
122
- - [Snapshots](#snapshots)
123
- - [Reindexing](#reindexing)
124
- - [Auth](#auth)
125
- - [Aggregation](#aggregation)
126
- - [Rate Limiting](#rate-limiting)
127
- - [Datetime-Based Index Management](#datetime-based-index-management)
87
+ - [stac-fastapi-elasticsearch-opensearch](#stac-fastapi-elasticsearch-opensearch)
88
+ - [Sponsors \& Supporters](#sponsors--supporters)
89
+ - [Project Introduction - What is SFEOS?](#project-introduction---what-is-sfeos)
90
+ - [Common Deployment Patterns](#common-deployment-patterns)
91
+ - [Technologies](#technologies)
92
+ - [Table of Contents](#table-of-contents)
93
+ - [Documentation \& Resources](#documentation--resources)
94
+ - [Package Structure](#package-structure)
95
+ - [Examples](#examples)
96
+ - [Performance](#performance)
97
+ - [Direct Response Mode](#direct-response-mode)
98
+ - [Quick Start](#quick-start)
99
+ - [Installation](#installation)
100
+ - [Running Locally](#running-locally)
101
+ - [Using Pre-built Docker Images](#using-pre-built-docker-images)
102
+ - [Using Docker Compose](#using-docker-compose)
103
+ - [Configuration Reference](#configuration-reference)
104
+ - [Datetime-Based Index Management](#datetime-based-index-management)
105
+ - [Overview](#overview)
106
+ - [When to Use](#when-to-use)
107
+ - [Configuration](#configuration)
108
+ - [Enabling Datetime-Based Indexing](#enabling-datetime-based-indexing)
109
+ - [Related Configuration Variables](#related-configuration-variables)
110
+ - [How Datetime-Based Indexing Works](#how-datetime-based-indexing-works)
111
+ - [Index and Alias Naming Convention](#index-and-alias-naming-convention)
112
+ - [Index Size Management](#index-size-management)
113
+ - [Interacting with the API](#interacting-with-the-api)
114
+ - [Configure the API](#configure-the-api)
115
+ - [Collection Pagination](#collection-pagination)
116
+ - [Ingesting Sample Data CLI Tool](#ingesting-sample-data-cli-tool)
117
+ - [Elasticsearch Mappings](#elasticsearch-mappings)
118
+ - [Managing Elasticsearch Indices](#managing-elasticsearch-indices)
119
+ - [Snapshots](#snapshots)
120
+ - [Reindexing](#reindexing)
121
+ - [Auth](#auth)
122
+ - [Aggregation](#aggregation)
123
+ - [Rate Limiting](#rate-limiting)
128
124
 
129
125
  ## Documentation & Resources
130
126
 
@@ -267,6 +263,8 @@ You can customize additional settings in your `.env` file:
267
263
  | `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 |
268
264
  | `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 |
269
265
  | `STAC_ITEM_LIMIT` | Sets the environment variable for result limiting to SFEOS for the number of returned items and STAC collections. | `10` | Optional |
266
+ | `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 |
267
+ | `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 |
270
268
 
271
269
  > [!NOTE]
272
270
  > 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.