stac-fastapi-opensearch 4.1.0__py3-none-any.whl → 4.2.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.
@@ -1,5 +1,6 @@
1
1
  """FastAPI application."""
2
2
 
3
+ import logging
3
4
  import os
4
5
  from contextlib import asynccontextmanager
5
6
 
@@ -23,6 +24,7 @@ from stac_fastapi.core.extensions.fields import FieldsExtension
23
24
  from stac_fastapi.core.rate_limit import setup_rate_limit
24
25
  from stac_fastapi.core.route_dependencies import get_route_dependencies
25
26
  from stac_fastapi.core.session import Session
27
+ from stac_fastapi.core.utilities import get_bool_env
26
28
  from stac_fastapi.extensions.core import (
27
29
  AggregationExtension,
28
30
  FilterExtension,
@@ -39,6 +41,12 @@ from stac_fastapi.opensearch.database_logic import (
39
41
  create_index_templates,
40
42
  )
41
43
 
44
+ logging.basicConfig(level=logging.INFO)
45
+ logger = logging.getLogger(__name__)
46
+
47
+ TRANSACTIONS_EXTENSIONS = get_bool_env("ENABLE_TRANSACTIONS_EXTENSIONS", default=True)
48
+ logger.info("TRANSACTIONS_EXTENSIONS is set to %s", TRANSACTIONS_EXTENSIONS)
49
+
42
50
  settings = OpensearchSettings()
43
51
  session = Session.create_from_settings(settings)
44
52
 
@@ -60,19 +68,6 @@ aggregation_extension.POST = EsAggregationExtensionPostRequest
60
68
  aggregation_extension.GET = EsAggregationExtensionGetRequest
61
69
 
62
70
  search_extensions = [
63
- TransactionExtension(
64
- client=TransactionsClient(
65
- database=database_logic, session=session, settings=settings
66
- ),
67
- settings=settings,
68
- ),
69
- BulkTransactionExtension(
70
- client=BulkTransactionsClient(
71
- database=database_logic,
72
- session=session,
73
- settings=settings,
74
- )
75
- ),
76
71
  FieldsExtension(),
77
72
  QueryExtension(),
78
73
  SortExtension(),
@@ -81,6 +76,28 @@ search_extensions = [
81
76
  FreeTextExtension(),
82
77
  ]
83
78
 
79
+
80
+ if TRANSACTIONS_EXTENSIONS:
81
+ search_extensions.insert(
82
+ 0,
83
+ TransactionExtension(
84
+ client=TransactionsClient(
85
+ database=database_logic, session=session, settings=settings
86
+ ),
87
+ settings=settings,
88
+ ),
89
+ )
90
+ search_extensions.insert(
91
+ 1,
92
+ BulkTransactionExtension(
93
+ client=BulkTransactionsClient(
94
+ database=database_logic,
95
+ session=session,
96
+ settings=settings,
97
+ )
98
+ ),
99
+ )
100
+
84
101
  extensions = [aggregation_extension] + search_extensions
85
102
 
86
103
  database_logic.extensions = [type(ext).__name__ for ext in extensions]
@@ -90,11 +107,14 @@ post_request_model = create_post_request_model(search_extensions)
90
107
  api = StacApi(
91
108
  title=os.getenv("STAC_FASTAPI_TITLE", "stac-fastapi-opensearch"),
92
109
  description=os.getenv("STAC_FASTAPI_DESCRIPTION", "stac-fastapi-opensearch"),
93
- api_version=os.getenv("STAC_FASTAPI_VERSION", "4.1.0"),
110
+ api_version=os.getenv("STAC_FASTAPI_VERSION", "4.2.0"),
94
111
  settings=settings,
95
112
  extensions=extensions,
96
113
  client=CoreClient(
97
- database=database_logic, session=session, post_request_model=post_request_model
114
+ database=database_logic,
115
+ session=session,
116
+ post_request_model=post_request_model,
117
+ landing_page_id=os.getenv("STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi"),
98
118
  ),
99
119
  search_get_request_model=create_get_request_model(search_extensions),
100
120
  search_post_request_model=post_request_model,
@@ -2,13 +2,13 @@
2
2
  import logging
3
3
  import os
4
4
  import ssl
5
- from typing import Any, Dict, Set
5
+ from typing import Any, Dict, Set, Union
6
6
 
7
7
  import certifi
8
8
  from opensearchpy import AsyncOpenSearch, OpenSearch
9
9
 
10
10
  from stac_fastapi.core.base_settings import ApiBaseSettings
11
- from stac_fastapi.core.utilities import get_bool_env
11
+ from stac_fastapi.core.utilities import get_bool_env, validate_refresh
12
12
  from stac_fastapi.types.config import ApiSettings
13
13
 
14
14
 
@@ -85,6 +85,17 @@ class OpensearchSettings(ApiSettings, ApiBaseSettings):
85
85
  enable_direct_response: bool = get_bool_env("ENABLE_DIRECT_RESPONSE", default=False)
86
86
  raise_on_bulk_error: bool = get_bool_env("RAISE_ON_BULK_ERROR", default=False)
87
87
 
88
+ @property
89
+ def database_refresh(self) -> Union[bool, str]:
90
+ """
91
+ Get the value of the DATABASE_REFRESH environment variable.
92
+
93
+ Returns:
94
+ Union[bool, str]: The value of DATABASE_REFRESH, which can be True, False, or "wait_for".
95
+ """
96
+ value = os.getenv("DATABASE_REFRESH", "false")
97
+ return validate_refresh(value)
98
+
88
99
  @property
89
100
  def create_client(self):
90
101
  """Create es client."""
@@ -106,6 +117,17 @@ class AsyncOpensearchSettings(ApiSettings, ApiBaseSettings):
106
117
  enable_direct_response: bool = get_bool_env("ENABLE_DIRECT_RESPONSE", default=False)
107
118
  raise_on_bulk_error: bool = get_bool_env("RAISE_ON_BULK_ERROR", default=False)
108
119
 
120
+ @property
121
+ def database_refresh(self) -> Union[bool, str]:
122
+ """
123
+ Get the value of the DATABASE_REFRESH environment variable.
124
+
125
+ Returns:
126
+ Union[bool, str]: The value of DATABASE_REFRESH, which can be True, False, or "wait_for".
127
+ """
128
+ value = os.getenv("DATABASE_REFRESH", "false")
129
+ return validate_refresh(value)
130
+
109
131
  @property
110
132
  def create_client(self):
111
133
  """Create async elasticsearch client."""
@@ -31,7 +31,7 @@ from stac_fastapi.core.database_logic import (
31
31
  )
32
32
  from stac_fastapi.core.extensions import filter
33
33
  from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
34
- from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon
34
+ from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon, validate_refresh
35
35
  from stac_fastapi.opensearch.config import (
36
36
  AsyncOpensearchSettings as AsyncSearchSettings,
37
37
  )
@@ -307,6 +307,34 @@ class DatabaseLogic(BaseDatabaseLogic):
307
307
  )
308
308
  return item["_source"]
309
309
 
310
+ async def get_queryables_mapping(self, collection_id: str = "*") -> dict:
311
+ """Retrieve mapping of Queryables for search.
312
+
313
+ Args:
314
+ collection_id (str, optional): The id of the Collection the Queryables
315
+ belongs to. Defaults to "*".
316
+
317
+ Returns:
318
+ dict: A dictionary containing the Queryables mappings.
319
+ """
320
+ queryables_mapping = {}
321
+
322
+ mappings = await self.client.indices.get_mapping(
323
+ index=f"{ITEMS_INDEX_PREFIX}{collection_id}",
324
+ )
325
+
326
+ for mapping in mappings.values():
327
+ fields = mapping["mappings"].get("properties", {})
328
+ properties = fields.pop("properties", {}).get("properties", {}).keys()
329
+
330
+ for field_key in fields:
331
+ queryables_mapping[field_key] = field_key
332
+
333
+ for property_key in properties:
334
+ queryables_mapping[property_key] = f"properties.{property_key}"
335
+
336
+ return queryables_mapping
337
+
310
338
  @staticmethod
311
339
  def make_search():
312
340
  """Database logic to create a Search instance."""
@@ -535,8 +563,9 @@ class DatabaseLogic(BaseDatabaseLogic):
535
563
 
536
564
  return search
537
565
 
538
- @staticmethod
539
- def apply_cql2_filter(search: Search, _filter: Optional[Dict[str, Any]]):
566
+ async def apply_cql2_filter(
567
+ self, search: Search, _filter: Optional[Dict[str, Any]]
568
+ ):
540
569
  """
541
570
  Apply a CQL2 filter to an Opensearch Search object.
542
571
 
@@ -556,7 +585,7 @@ class DatabaseLogic(BaseDatabaseLogic):
556
585
  otherwise the original Search object.
557
586
  """
558
587
  if _filter is not None:
559
- es_query = filter.to_es(_filter)
588
+ es_query = filter.to_es(await self.get_queryables_mapping(), _filter)
560
589
  search = search.filter(es_query)
561
590
 
562
591
  return search
@@ -864,15 +893,17 @@ class DatabaseLogic(BaseDatabaseLogic):
864
893
  async def create_item(
865
894
  self,
866
895
  item: Item,
867
- refresh: bool = False,
868
896
  base_url: str = "",
869
897
  exist_ok: bool = False,
898
+ **kwargs: Any,
870
899
  ):
871
900
  """Database logic for creating one item.
872
901
 
873
902
  Args:
874
903
  item (Item): The item to be created.
875
- refresh (bool, optional): Refresh the index after performing the operation. Defaults to False.
904
+ base_url (str, optional): The base URL for the item. Defaults to an empty string.
905
+ exist_ok (bool, optional): Whether to allow the item to exist already. Defaults to False.
906
+ **kwargs: Additional keyword arguments like refresh.
876
907
 
877
908
  Raises:
878
909
  ConflictError: If the item already exists in the database.
@@ -883,6 +914,19 @@ class DatabaseLogic(BaseDatabaseLogic):
883
914
  # todo: check if collection exists, but cache
884
915
  item_id = item["id"]
885
916
  collection_id = item["collection"]
917
+
918
+ # Ensure kwargs is a dictionary
919
+ kwargs = kwargs or {}
920
+
921
+ # Resolve the `refresh` parameter
922
+ refresh = kwargs.get("refresh", self.async_settings.database_refresh)
923
+ refresh = validate_refresh(refresh)
924
+
925
+ # Log the creation attempt
926
+ logger.info(
927
+ f"Creating item {item_id} in collection {collection_id} with refresh={refresh}"
928
+ )
929
+
886
930
  item = await self.async_prep_create_item(
887
931
  item=item, base_url=base_url, exist_ok=exist_ok
888
932
  )
@@ -893,19 +937,29 @@ class DatabaseLogic(BaseDatabaseLogic):
893
937
  refresh=refresh,
894
938
  )
895
939
 
896
- async def delete_item(
897
- self, item_id: str, collection_id: str, refresh: bool = False
898
- ):
940
+ async def delete_item(self, item_id: str, collection_id: str, **kwargs: Any):
899
941
  """Delete a single item from the database.
900
942
 
901
943
  Args:
902
944
  item_id (str): The id of the Item to be deleted.
903
945
  collection_id (str): The id of the Collection that the Item belongs to.
904
- refresh (bool, optional): Whether to refresh the index after the deletion. Default is False.
946
+ **kwargs: Additional keyword arguments like refresh.
905
947
 
906
948
  Raises:
907
949
  NotFoundError: If the Item does not exist in the database.
908
950
  """
951
+ # Ensure kwargs is a dictionary
952
+ kwargs = kwargs or {}
953
+
954
+ # Resolve the `refresh` parameter
955
+ refresh = kwargs.get("refresh", self.async_settings.database_refresh)
956
+ refresh = validate_refresh(refresh)
957
+
958
+ # Log the deletion attempt
959
+ logger.info(
960
+ f"Deleting item {item_id} from collection {collection_id} with refresh={refresh}"
961
+ )
962
+
909
963
  try:
910
964
  await self.client.delete(
911
965
  index=index_alias_by_collection_id(collection_id),
@@ -935,12 +989,12 @@ class DatabaseLogic(BaseDatabaseLogic):
935
989
  except exceptions.NotFoundError:
936
990
  raise NotFoundError(f"Mapping for index {index_name} not found")
937
991
 
938
- async def create_collection(self, collection: Collection, refresh: bool = False):
992
+ async def create_collection(self, collection: Collection, **kwargs: Any):
939
993
  """Create a single collection in the database.
940
994
 
941
995
  Args:
942
996
  collection (Collection): The Collection object to be created.
943
- refresh (bool, optional): Whether to refresh the index after the creation. Default is False.
997
+ **kwargs: Additional keyword arguments like refresh.
944
998
 
945
999
  Raises:
946
1000
  ConflictError: If a Collection with the same id already exists in the database.
@@ -950,6 +1004,16 @@ class DatabaseLogic(BaseDatabaseLogic):
950
1004
  """
951
1005
  collection_id = collection["id"]
952
1006
 
1007
+ # Ensure kwargs is a dictionary
1008
+ kwargs = kwargs or {}
1009
+
1010
+ # Resolve the `refresh` parameter
1011
+ refresh = kwargs.get("refresh", self.async_settings.database_refresh)
1012
+ refresh = validate_refresh(refresh)
1013
+
1014
+ # Log the creation attempt
1015
+ logger.info(f"Creating collection {collection_id} with refresh={refresh}")
1016
+
953
1017
  if await self.client.exists(index=COLLECTIONS_INDEX, id=collection_id):
954
1018
  raise ConflictError(f"Collection {collection_id} already exists")
955
1019
 
@@ -989,14 +1053,14 @@ class DatabaseLogic(BaseDatabaseLogic):
989
1053
  return collection["_source"]
990
1054
 
991
1055
  async def update_collection(
992
- self, collection_id: str, collection: Collection, refresh: bool = False
1056
+ self, collection_id: str, collection: Collection, **kwargs: Any
993
1057
  ):
994
1058
  """Update a collection from the database.
995
1059
 
996
1060
  Args:
997
- self: The instance of the object calling this function.
998
1061
  collection_id (str): The ID of the collection to be updated.
999
1062
  collection (Collection): The Collection object to be used for the update.
1063
+ **kwargs: Additional keyword arguments like refresh.
1000
1064
 
1001
1065
  Raises:
1002
1066
  NotFoundError: If the collection with the given `collection_id` is not
@@ -1007,9 +1071,23 @@ class DatabaseLogic(BaseDatabaseLogic):
1007
1071
  `collection_id` and with the collection specified in the `Collection` object.
1008
1072
  If the collection is not found, a `NotFoundError` is raised.
1009
1073
  """
1074
+ # Ensure kwargs is a dictionary
1075
+ kwargs = kwargs or {}
1076
+
1077
+ # Resolve the `refresh` parameter
1078
+ refresh = kwargs.get("refresh", self.async_settings.database_refresh)
1079
+ refresh = validate_refresh(refresh)
1080
+
1081
+ # Log the update attempt
1082
+ logger.info(f"Updating collection {collection_id} with refresh={refresh}")
1083
+
1010
1084
  await self.find_collection(collection_id=collection_id)
1011
1085
 
1012
1086
  if collection_id != collection["id"]:
1087
+ logger.info(
1088
+ f"Collection ID change detected: {collection_id} -> {collection['id']}"
1089
+ )
1090
+
1013
1091
  await self.create_collection(collection, refresh=refresh)
1014
1092
 
1015
1093
  await self.client.reindex(
@@ -1025,7 +1103,7 @@ class DatabaseLogic(BaseDatabaseLogic):
1025
1103
  refresh=refresh,
1026
1104
  )
1027
1105
 
1028
- await self.delete_collection(collection_id)
1106
+ await self.delete_collection(collection_id=collection_id, **kwargs)
1029
1107
 
1030
1108
  else:
1031
1109
  await self.client.index(
@@ -1035,23 +1113,34 @@ class DatabaseLogic(BaseDatabaseLogic):
1035
1113
  refresh=refresh,
1036
1114
  )
1037
1115
 
1038
- async def delete_collection(self, collection_id: str, refresh: bool = False):
1116
+ async def delete_collection(self, collection_id: str, **kwargs: Any):
1039
1117
  """Delete a collection from the database.
1040
1118
 
1041
1119
  Parameters:
1042
1120
  self: The instance of the object calling this function.
1043
1121
  collection_id (str): The ID of the collection to be deleted.
1044
- refresh (bool): Whether to refresh the index after the deletion (default: False).
1122
+ **kwargs: Additional keyword arguments like refresh.
1045
1123
 
1046
1124
  Raises:
1047
1125
  NotFoundError: If the collection with the given `collection_id` is not found in the database.
1048
1126
 
1049
1127
  Notes:
1050
1128
  This function first verifies that the collection with the specified `collection_id` exists in the database, and then
1051
- deletes the collection. If `refresh` is set to True, the index is refreshed after the deletion. Additionally, this
1052
- function also calls `delete_item_index` to delete the index for the items in the collection.
1129
+ deletes the collection. If `refresh` is set to "true", "false", or "wait_for", the index is refreshed accordingly after
1130
+ the deletion. Additionally, this function also calls `delete_item_index` to delete the index for the items in the collection.
1053
1131
  """
1132
+ # Ensure kwargs is a dictionary
1133
+ kwargs = kwargs or {}
1134
+
1054
1135
  await self.find_collection(collection_id=collection_id)
1136
+
1137
+ # Resolve the `refresh` parameter
1138
+ refresh = kwargs.get("refresh", self.async_settings.database_refresh)
1139
+ refresh = validate_refresh(refresh)
1140
+
1141
+ # Log the deletion attempt
1142
+ logger.info(f"Deleting collection {collection_id} with refresh={refresh}")
1143
+
1055
1144
  await self.client.delete(
1056
1145
  index=COLLECTIONS_INDEX, id=collection_id, refresh=refresh
1057
1146
  )
@@ -1061,7 +1150,7 @@ class DatabaseLogic(BaseDatabaseLogic):
1061
1150
  self,
1062
1151
  collection_id: str,
1063
1152
  processed_items: List[Item],
1064
- refresh: bool = False,
1153
+ **kwargs: Any,
1065
1154
  ) -> Tuple[int, List[Dict[str, Any]]]:
1066
1155
  """
1067
1156
  Perform a bulk insert of items into the database asynchronously.
@@ -1069,7 +1158,12 @@ class DatabaseLogic(BaseDatabaseLogic):
1069
1158
  Args:
1070
1159
  collection_id (str): The ID of the collection to which the items belong.
1071
1160
  processed_items (List[Item]): A list of `Item` objects to be inserted into the database.
1072
- refresh (bool): Whether to refresh the index after the bulk insert (default: False).
1161
+ **kwargs (Any): Additional keyword arguments, including:
1162
+ - refresh (str, optional): Whether to refresh the index after the bulk insert.
1163
+ Can be "true", "false", or "wait_for". Defaults to the value of `self.sync_settings.database_refresh`.
1164
+ - refresh (bool, optional): Whether to refresh the index after the bulk insert.
1165
+ - raise_on_error (bool, optional): Whether to raise an error if any of the bulk operations fail.
1166
+ Defaults to the value of `self.async_settings.raise_on_bulk_error`.
1073
1167
 
1074
1168
  Returns:
1075
1169
  Tuple[int, List[Dict[str, Any]]]: A tuple containing:
@@ -1078,10 +1172,30 @@ class DatabaseLogic(BaseDatabaseLogic):
1078
1172
 
1079
1173
  Notes:
1080
1174
  This function performs a bulk insert of `processed_items` into the database using the specified `collection_id`.
1081
- The insert is performed asynchronously, and the event loop is used to run the operation in a separate executor.
1082
- The `mk_actions` function is called to generate a list of actions for the bulk insert. If `refresh` is set to True,
1083
- the index is refreshed after the bulk insert.
1175
+ The insert is performed synchronously and blocking, meaning that the function does not return until the insert has
1176
+ completed. The `mk_actions` function is called to generate a list of actions for the bulk insert. The `refresh`
1177
+ parameter determines whether the index is refreshed after the bulk insert:
1178
+ - "true": Forces an immediate refresh of the index.
1179
+ - "false": Does not refresh the index immediately (default behavior).
1180
+ - "wait_for": Waits for the next refresh cycle to make the changes visible.
1084
1181
  """
1182
+ # Ensure kwargs is a dictionary
1183
+ kwargs = kwargs or {}
1184
+
1185
+ # Resolve the `refresh` parameter
1186
+ refresh = kwargs.get("refresh", self.async_settings.database_refresh)
1187
+ refresh = validate_refresh(refresh)
1188
+
1189
+ # Log the bulk insert attempt
1190
+ logger.info(
1191
+ f"Performing bulk insert for collection {collection_id} with refresh={refresh}"
1192
+ )
1193
+
1194
+ # Handle empty processed_items
1195
+ if not processed_items:
1196
+ logger.warning(f"No items to insert for collection {collection_id}")
1197
+ return 0, []
1198
+
1085
1199
  raise_on_error = self.async_settings.raise_on_bulk_error
1086
1200
  success, errors = await helpers.async_bulk(
1087
1201
  self.client,
@@ -1089,21 +1203,30 @@ class DatabaseLogic(BaseDatabaseLogic):
1089
1203
  refresh=refresh,
1090
1204
  raise_on_error=raise_on_error,
1091
1205
  )
1206
+ # Log the result
1207
+ logger.info(
1208
+ f"Bulk insert completed for collection {collection_id}: {success} successes, {len(errors)} errors"
1209
+ )
1092
1210
  return success, errors
1093
1211
 
1094
1212
  def bulk_sync(
1095
1213
  self,
1096
1214
  collection_id: str,
1097
1215
  processed_items: List[Item],
1098
- refresh: bool = False,
1216
+ **kwargs: Any,
1099
1217
  ) -> Tuple[int, List[Dict[str, Any]]]:
1100
1218
  """
1101
- Perform a bulk insert of items into the database synchronously.
1219
+ Perform a bulk insert of items into the database asynchronously.
1102
1220
 
1103
1221
  Args:
1104
1222
  collection_id (str): The ID of the collection to which the items belong.
1105
1223
  processed_items (List[Item]): A list of `Item` objects to be inserted into the database.
1106
- refresh (bool): Whether to refresh the index after the bulk insert (default: False).
1224
+ **kwargs (Any): Additional keyword arguments, including:
1225
+ - refresh (str, optional): Whether to refresh the index after the bulk insert.
1226
+ Can be "true", "false", or "wait_for". Defaults to the value of `self.sync_settings.database_refresh`.
1227
+ - refresh (bool, optional): Whether to refresh the index after the bulk insert.
1228
+ - raise_on_error (bool, optional): Whether to raise an error if any of the bulk operations fail.
1229
+ Defaults to the value of `self.async_settings.raise_on_bulk_error`.
1107
1230
 
1108
1231
  Returns:
1109
1232
  Tuple[int, List[Dict[str, Any]]]: A tuple containing:
@@ -1113,9 +1236,29 @@ class DatabaseLogic(BaseDatabaseLogic):
1113
1236
  Notes:
1114
1237
  This function performs a bulk insert of `processed_items` into the database using the specified `collection_id`.
1115
1238
  The insert is performed synchronously and blocking, meaning that the function does not return until the insert has
1116
- completed. The `mk_actions` function is called to generate a list of actions for the bulk insert. If `refresh` is set to
1117
- True, the index is refreshed after the bulk insert.
1239
+ completed. The `mk_actions` function is called to generate a list of actions for the bulk insert. The `refresh`
1240
+ parameter determines whether the index is refreshed after the bulk insert:
1241
+ - "true": Forces an immediate refresh of the index.
1242
+ - "false": Does not refresh the index immediately (default behavior).
1243
+ - "wait_for": Waits for the next refresh cycle to make the changes visible.
1118
1244
  """
1245
+ # Ensure kwargs is a dictionary
1246
+ kwargs = kwargs or {}
1247
+
1248
+ # Resolve the `refresh` parameter
1249
+ refresh = kwargs.get("refresh", self.async_settings.database_refresh)
1250
+ refresh = validate_refresh(refresh)
1251
+
1252
+ # Log the bulk insert attempt
1253
+ logger.info(
1254
+ f"Performing bulk insert for collection {collection_id} with refresh={refresh}"
1255
+ )
1256
+
1257
+ # Handle empty processed_items
1258
+ if not processed_items:
1259
+ logger.warning(f"No items to insert for collection {collection_id}")
1260
+ return 0, []
1261
+
1119
1262
  raise_on_error = self.sync_settings.raise_on_bulk_error
1120
1263
  success, errors = helpers.bulk(
1121
1264
  self.sync_client,
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "4.1.0"
2
+ __version__ = "4.2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi-opensearch
3
- Version: 4.1.0
3
+ Version: 4.2.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,7 +15,7 @@ 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==4.1.0
18
+ Requires-Dist: stac-fastapi-core==4.2.0
19
19
  Requires-Dist: opensearch-py~=2.8.0
20
20
  Requires-Dist: opensearch-py[async]~=2.8.0
21
21
  Requires-Dist: uvicorn~=0.23.0
@@ -141,6 +141,7 @@ You can customize additional settings in your `.env` file:
141
141
  | `STAC_FASTAPI_TITLE` | Title of the API in the documentation. | `stac-fastapi-elasticsearch` or `stac-fastapi-opensearch` | Optional |
142
142
  | `STAC_FASTAPI_DESCRIPTION` | Description of the API in the documentation. | N/A | Optional |
143
143
  | `STAC_FASTAPI_VERSION` | API version. | `2.1` | Optional |
144
+ | `STAC_FASTAPI_LANDING_PAGE_ID` | Landing page ID | `stac-fastapi` | Optional |
144
145
  | `APP_HOST` | Server bind address. | `0.0.0.0` | Optional |
145
146
  | `APP_PORT` | Server port. | `8080` | Optional |
146
147
  | `ENVIRONMENT` | Runtime environment. | `local` | Optional |
@@ -148,10 +149,12 @@ You can customize additional settings in your `.env` file:
148
149
  | `RELOAD` | Enable auto-reload for development. | `true` | Optional |
149
150
  | `STAC_FASTAPI_RATE_LIMIT` | API rate limit per client. | `200/minute` | Optional |
150
151
  | `BACKEND` | Tests-related variable | `elasticsearch` or `opensearch` based on the backend | Optional |
151
- | `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional |
152
- | `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
152
+ | `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional | |
153
153
  | `OPENSEARCH_VERSION` | OpenSearch version | `2.11.1` | Optional
154
- | `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 |
154
+ | `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional
155
+ | `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 |
156
+ | `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 |
157
+ | `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 |
155
158
 
156
159
  > [!NOTE]
157
160
  > The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, and `ES_VERIFY_CERTS` 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.
@@ -0,0 +1,10 @@
1
+ stac_fastapi/opensearch/__init__.py,sha256=iJWMUgn7mUvmuPQSO_FlyhJ5eDdbbfmGv1qnFOX5-qk,28
2
+ stac_fastapi/opensearch/app.py,sha256=xHZ7Mw2GtI99B0kS2Zl3YvD4WSzH2bqImjcZib54THc,4963
3
+ stac_fastapi/opensearch/config.py,sha256=kml8H73WycThr5VC7DCCfpE76uPYEc3bjdJDqewIGis,5014
4
+ stac_fastapi/opensearch/database_logic.py,sha256=mwbEfquoqkp8FyfgvW5d3pUJ6jz_igQDSAbPfDVXzpc,49064
5
+ stac_fastapi/opensearch/version.py,sha256=XAYr-IO1hoDdSshTkYzWFp3wj4AdjSQwUik30pTEaAo,45
6
+ stac_fastapi_opensearch-4.2.0.dist-info/METADATA,sha256=ETSpAW1EhE4d8-g5Xgn94WPnHc2_4wOWYRny_IgNu7c,21870
7
+ stac_fastapi_opensearch-4.2.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
8
+ stac_fastapi_opensearch-4.2.0.dist-info/entry_points.txt,sha256=zjZ0Xsr9BUNJqMkdPpl6zEIUykv1uFdJtNELFRChp0w,76
9
+ stac_fastapi_opensearch-4.2.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
10
+ stac_fastapi_opensearch-4.2.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=VL7kL0PvRuMMAHJ-0AsoqvpIov4lFilelXW8ezVNBoU,4376
3
- stac_fastapi/opensearch/config.py,sha256=T4nFpe3cOfKagUSkslXHIHHB0FHCOwsI1adZaLGi2WY,4243
4
- stac_fastapi/opensearch/database_logic.py,sha256=qMqDjsfOFFBSXx2RCMZLOPithqfH5qLVY0Goj2c7uYA,43197
5
- stac_fastapi/opensearch/version.py,sha256=3xU5aBmxgAcPizRlef_YROCW9ULsGOA4flSyd9AQog4,45
6
- stac_fastapi_opensearch-4.1.0.dist-info/METADATA,sha256=4L2JDka8J23K1jXn8NOfY__spCBwjSPke6DuCiVr8B8,20845
7
- stac_fastapi_opensearch-4.1.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
8
- stac_fastapi_opensearch-4.1.0.dist-info/entry_points.txt,sha256=zjZ0Xsr9BUNJqMkdPpl6zEIUykv1uFdJtNELFRChp0w,76
9
- stac_fastapi_opensearch-4.1.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
10
- stac_fastapi_opensearch-4.1.0.dist-info/RECORD,,