stac-fastapi-opensearch 6.8.1__tar.gz → 6.10.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.
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/.gitignore +11 -0
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/PKG-INFO +5 -5
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/pyproject.toml +4 -4
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/app.py +2 -2
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/database_logic.py +92 -47
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/version.py +1 -1
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/README.md +0 -0
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/pytest.ini +0 -0
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/__init__.py +0 -0
- {stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/config.py +0 -0
|
@@ -141,3 +141,14 @@ venv
|
|
|
141
141
|
/docs/src/api/*
|
|
142
142
|
|
|
143
143
|
.DS_Store
|
|
144
|
+
|
|
145
|
+
# Helm
|
|
146
|
+
*.tgz
|
|
147
|
+
charts/*/charts/
|
|
148
|
+
charts/*/requirements.lock
|
|
149
|
+
charts/*/Chart.lock
|
|
150
|
+
helm-chart/stac-fastapi/charts/
|
|
151
|
+
helm-chart/stac-fastapi/Chart.lock
|
|
152
|
+
helm-chart/stac-fastapi/*.tgz
|
|
153
|
+
helm-chart/test-results/
|
|
154
|
+
helm-chart/tmp/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stac_fastapi_opensearch
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.10.0
|
|
4
4
|
Summary: An implementation of STAC API based on the FastAPI framework with Opensearch.
|
|
5
5
|
Project-URL: Homepage, https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
|
|
6
6
|
License: MIT
|
|
@@ -16,8 +16,8 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
16
16
|
Requires-Python: >=3.11
|
|
17
17
|
Requires-Dist: opensearch-py[async]~=2.8.0
|
|
18
18
|
Requires-Dist: opensearch-py~=2.8.0
|
|
19
|
-
Requires-Dist: sfeos-helpers==6.
|
|
20
|
-
Requires-Dist: stac-fastapi-core==6.
|
|
19
|
+
Requires-Dist: sfeos-helpers==6.10.0
|
|
20
|
+
Requires-Dist: stac-fastapi-core==6.10.0
|
|
21
21
|
Requires-Dist: starlette<0.36.0,>=0.35.0
|
|
22
22
|
Requires-Dist: uvicorn~=0.23.0
|
|
23
23
|
Provides-Extra: dev
|
|
@@ -29,13 +29,13 @@ Requires-Dist: pytest-cov~=4.0.0; extra == 'dev'
|
|
|
29
29
|
Requires-Dist: pytest~=8.0; extra == 'dev'
|
|
30
30
|
Requires-Dist: redis~=6.4.0; extra == 'dev'
|
|
31
31
|
Requires-Dist: retry~=0.9.2; extra == 'dev'
|
|
32
|
-
Requires-Dist: stac-fastapi-core[redis]==6.
|
|
32
|
+
Requires-Dist: stac-fastapi-core[redis]==6.10.0; extra == 'dev'
|
|
33
33
|
Provides-Extra: docs
|
|
34
34
|
Requires-Dist: mkdocs-material~=9.0.0; extra == 'docs'
|
|
35
35
|
Requires-Dist: mkdocs~=1.4.0; extra == 'docs'
|
|
36
36
|
Requires-Dist: pdocs~=1.2.0; extra == 'docs'
|
|
37
37
|
Provides-Extra: redis
|
|
38
|
-
Requires-Dist: stac-fastapi-core[redis]==6.
|
|
38
|
+
Requires-Dist: stac-fastapi-core[redis]==6.10.0; extra == 'redis'
|
|
39
39
|
Provides-Extra: server
|
|
40
40
|
Requires-Dist: uvicorn[standard]~=0.23.0; extra == 'server'
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
@@ -28,8 +28,8 @@ keywords = [
|
|
|
28
28
|
]
|
|
29
29
|
dynamic = ["version"]
|
|
30
30
|
dependencies = [
|
|
31
|
-
"stac-fastapi-core==6.
|
|
32
|
-
"sfeos-helpers==6.
|
|
31
|
+
"stac-fastapi-core==6.10.0",
|
|
32
|
+
"sfeos-helpers==6.10.0",
|
|
33
33
|
"opensearch-py~=2.8.0",
|
|
34
34
|
"opensearch-py[async]~=2.8.0",
|
|
35
35
|
"uvicorn~=0.23.0",
|
|
@@ -49,7 +49,7 @@ dev = [
|
|
|
49
49
|
"httpx>=0.24.0,<0.28.0",
|
|
50
50
|
"redis~=6.4.0",
|
|
51
51
|
"retry~=0.9.2",
|
|
52
|
-
"stac-fastapi-core[redis]==6.
|
|
52
|
+
"stac-fastapi-core[redis]==6.10.0",
|
|
53
53
|
]
|
|
54
54
|
docs = [
|
|
55
55
|
"mkdocs~=1.4.0",
|
|
@@ -57,7 +57,7 @@ docs = [
|
|
|
57
57
|
"pdocs~=1.2.0",
|
|
58
58
|
]
|
|
59
59
|
redis = [
|
|
60
|
-
"stac-fastapi-core[redis]==6.
|
|
60
|
+
"stac-fastapi-core[redis]==6.10.0",
|
|
61
61
|
]
|
|
62
62
|
server = [
|
|
63
63
|
"uvicorn[standard]~=0.23.0",
|
{stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/app.py
RENAMED
|
@@ -214,7 +214,7 @@ if ENABLE_CATALOGS_ROUTE:
|
|
|
214
214
|
),
|
|
215
215
|
settings=settings,
|
|
216
216
|
conformance_classes=[
|
|
217
|
-
"https://api.stacspec.org/v1.0.0-beta.1/catalogs
|
|
217
|
+
"https://api.stacspec.org/v1.0.0-beta.1/multi-tenant-catalogs",
|
|
218
218
|
],
|
|
219
219
|
)
|
|
220
220
|
extensions.append(catalogs_extension)
|
|
@@ -243,7 +243,7 @@ items_get_request_model = create_request_model(
|
|
|
243
243
|
app_config = {
|
|
244
244
|
"title": os.getenv("STAC_FASTAPI_TITLE", "stac-fastapi-opensearch"),
|
|
245
245
|
"description": os.getenv("STAC_FASTAPI_DESCRIPTION", "stac-fastapi-opensearch"),
|
|
246
|
-
"api_version": os.getenv("STAC_FASTAPI_VERSION", "6.
|
|
246
|
+
"api_version": os.getenv("STAC_FASTAPI_VERSION", "6.10.0"),
|
|
247
247
|
"settings": settings,
|
|
248
248
|
"extensions": extensions,
|
|
249
249
|
"client": CoreClient(
|
|
@@ -49,6 +49,7 @@ from stac_fastapi.sfeos_helpers.database.query import (
|
|
|
49
49
|
add_collections_to_body,
|
|
50
50
|
)
|
|
51
51
|
from stac_fastapi.sfeos_helpers.database.utils import (
|
|
52
|
+
add_hidden_filter,
|
|
52
53
|
merge_to_operations,
|
|
53
54
|
operations_to_script,
|
|
54
55
|
)
|
|
@@ -64,6 +65,7 @@ from stac_fastapi.sfeos_helpers.mappings import (
|
|
|
64
65
|
from stac_fastapi.sfeos_helpers.search_engine import (
|
|
65
66
|
BaseIndexInserter,
|
|
66
67
|
BaseIndexSelector,
|
|
68
|
+
DatetimeIndexInserter,
|
|
67
69
|
IndexInsertionFactory,
|
|
68
70
|
IndexSelectorFactory,
|
|
69
71
|
)
|
|
@@ -406,12 +408,22 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
406
408
|
Notes:
|
|
407
409
|
The Item is retrieved from the Opensearch database using the `client.get` method,
|
|
408
410
|
with the index for the Collection as the target index and the combined `mk_item_id` as the document id.
|
|
411
|
+
Item is hidden if hide_item_path is configured via env var.
|
|
409
412
|
"""
|
|
410
413
|
try:
|
|
414
|
+
base_query = {"term": {"_id": mk_item_id(item_id, collection_id)}}
|
|
415
|
+
|
|
416
|
+
HIDE_ITEM_PATH = os.getenv("HIDE_ITEM_PATH", None)
|
|
417
|
+
|
|
418
|
+
if HIDE_ITEM_PATH:
|
|
419
|
+
query = add_hidden_filter(base_query, HIDE_ITEM_PATH)
|
|
420
|
+
else:
|
|
421
|
+
query = base_query
|
|
422
|
+
|
|
411
423
|
response = await self.client.search(
|
|
412
424
|
index=index_alias_by_collection_id(collection_id),
|
|
413
425
|
body={
|
|
414
|
-
"query":
|
|
426
|
+
"query": query,
|
|
415
427
|
"size": 1,
|
|
416
428
|
},
|
|
417
429
|
)
|
|
@@ -811,7 +823,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
811
823
|
token: Optional[str],
|
|
812
824
|
sort: Optional[Dict[str, Dict[str, str]]],
|
|
813
825
|
collection_ids: Optional[List[str]],
|
|
814
|
-
datetime_search:
|
|
826
|
+
datetime_search: str,
|
|
815
827
|
ignore_unavailable: bool = True,
|
|
816
828
|
) -> Tuple[Iterable[Dict[str, Any]], Optional[int], Optional[str]]:
|
|
817
829
|
"""Execute a search query with limit and other optional parameters.
|
|
@@ -822,7 +834,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
822
834
|
token (Optional[str]): The token used to return the next set of results.
|
|
823
835
|
sort (Optional[Dict[str, Dict[str, str]]]): Specifies how the results should be sorted.
|
|
824
836
|
collection_ids (Optional[List[str]]): The collection ids to search.
|
|
825
|
-
datetime_search (
|
|
837
|
+
datetime_search (str): Datetime used for index selection.
|
|
826
838
|
ignore_unavailable (bool, optional): Whether to ignore unavailable collections. Defaults to True.
|
|
827
839
|
|
|
828
840
|
Returns:
|
|
@@ -846,7 +858,11 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
846
858
|
index_param = ITEM_INDICES
|
|
847
859
|
query = add_collections_to_body(collection_ids, query)
|
|
848
860
|
|
|
849
|
-
|
|
861
|
+
HIDE_ITEM_PATH = os.getenv("HIDE_ITEM_PATH", None)
|
|
862
|
+
|
|
863
|
+
if HIDE_ITEM_PATH:
|
|
864
|
+
search_body["query"] = add_hidden_filter(query, HIDE_ITEM_PATH)
|
|
865
|
+
elif query:
|
|
850
866
|
search_body["query"] = query
|
|
851
867
|
|
|
852
868
|
search_after = None
|
|
@@ -871,11 +887,17 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
871
887
|
)
|
|
872
888
|
)
|
|
873
889
|
|
|
890
|
+
# Ensure hidden item is not counted
|
|
891
|
+
count_query = search.to_dict(count=True)
|
|
892
|
+
if HIDE_ITEM_PATH:
|
|
893
|
+
q = count_query.get("query")
|
|
894
|
+
count_query["query"] = add_hidden_filter(q, HIDE_ITEM_PATH)
|
|
895
|
+
|
|
874
896
|
count_task = asyncio.create_task(
|
|
875
897
|
self.client.count(
|
|
876
898
|
index=index_param,
|
|
877
899
|
ignore_unavailable=ignore_unavailable,
|
|
878
|
-
body=
|
|
900
|
+
body=count_query,
|
|
879
901
|
)
|
|
880
902
|
)
|
|
881
903
|
|
|
@@ -918,7 +940,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
918
940
|
geometry_geohash_grid_precision: int,
|
|
919
941
|
geometry_geotile_grid_precision: int,
|
|
920
942
|
datetime_frequency_interval: str,
|
|
921
|
-
datetime_search,
|
|
943
|
+
datetime_search: str,
|
|
922
944
|
ignore_unavailable: Optional[bool] = True,
|
|
923
945
|
):
|
|
924
946
|
"""Return aggregations of STAC Items."""
|
|
@@ -1096,19 +1118,25 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1096
1118
|
raise NotFoundError(f"Collection {item['collection']} does not exist")
|
|
1097
1119
|
|
|
1098
1120
|
# Check if the item already exists in the database
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1121
|
+
alias = index_alias_by_collection_id(item["collection"])
|
|
1122
|
+
doc_id = mk_item_id(item["id"], item["collection"])
|
|
1123
|
+
|
|
1124
|
+
if not exist_ok:
|
|
1125
|
+
alias_exists = self.sync_client.indices.exists_alias(name=alias)
|
|
1126
|
+
|
|
1127
|
+
if alias_exists:
|
|
1128
|
+
alias_info = self.sync_client.indices.get_alias(name=alias)
|
|
1129
|
+
indices = list(alias_info.keys())
|
|
1130
|
+
|
|
1131
|
+
for index in indices:
|
|
1132
|
+
if self.sync_client.exists(index=index, id=doc_id):
|
|
1133
|
+
error_message = f"Item {item['id']} in collection {item['collection']} already exists."
|
|
1134
|
+
if self.sync_settings.raise_on_bulk_error:
|
|
1135
|
+
raise ConflictError(error_message)
|
|
1136
|
+
else:
|
|
1137
|
+
logger.warning(
|
|
1138
|
+
f"{error_message} Continuing as `RAISE_ON_BULK_ERROR` is set to false."
|
|
1139
|
+
)
|
|
1112
1140
|
|
|
1113
1141
|
# Serialize the item into a database-compatible format
|
|
1114
1142
|
prepped_item = self.item_serializer.stac_to_db(item, base_url)
|
|
@@ -1152,6 +1180,31 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1152
1180
|
f"Creating item {item_id} in collection {collection_id} with refresh={refresh}"
|
|
1153
1181
|
)
|
|
1154
1182
|
|
|
1183
|
+
if exist_ok and isinstance(self.async_index_inserter, DatetimeIndexInserter):
|
|
1184
|
+
existing_item = await self.get_one_item(collection_id, item_id)
|
|
1185
|
+
primary_datetime_name = self.async_index_inserter.primary_datetime_name
|
|
1186
|
+
|
|
1187
|
+
existing_primary_datetime = existing_item.get("properties", {}).get(
|
|
1188
|
+
primary_datetime_name
|
|
1189
|
+
)
|
|
1190
|
+
new_primary_datetime = item.get("properties", {}).get(primary_datetime_name)
|
|
1191
|
+
|
|
1192
|
+
if existing_primary_datetime != new_primary_datetime:
|
|
1193
|
+
self.async_index_inserter.validate_datetime_field_update(
|
|
1194
|
+
f"properties/{primary_datetime_name}"
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
if primary_datetime_name == "start_datetime":
|
|
1198
|
+
existing_end_datetime = existing_item.get("properties", {}).get(
|
|
1199
|
+
"end_datetime"
|
|
1200
|
+
)
|
|
1201
|
+
new_end_datetime = item.get("properties", {}).get("end_datetime")
|
|
1202
|
+
|
|
1203
|
+
if existing_end_datetime != new_end_datetime:
|
|
1204
|
+
self.async_index_inserter.validate_datetime_field_update(
|
|
1205
|
+
"properties/end_datetime"
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1155
1208
|
item = await self.async_prep_create_item(
|
|
1156
1209
|
item=item, base_url=base_url, exist_ok=exist_ok
|
|
1157
1210
|
)
|
|
@@ -1219,6 +1272,10 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1219
1272
|
Returns:
|
|
1220
1273
|
patched item.
|
|
1221
1274
|
"""
|
|
1275
|
+
for operation in operations:
|
|
1276
|
+
if operation.op in ["add", "replace", "remove"]:
|
|
1277
|
+
self.async_index_inserter.validate_datetime_field_update(operation.path)
|
|
1278
|
+
|
|
1222
1279
|
new_item_id = None
|
|
1223
1280
|
new_collection_id = None
|
|
1224
1281
|
script_operations = []
|
|
@@ -1238,8 +1295,6 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1238
1295
|
else:
|
|
1239
1296
|
script_operations.append(operation)
|
|
1240
1297
|
|
|
1241
|
-
script = operations_to_script(script_operations, create_nest=create_nest)
|
|
1242
|
-
|
|
1243
1298
|
try:
|
|
1244
1299
|
search_response = await self.client.search(
|
|
1245
1300
|
index=index_alias_by_collection_id(collection_id),
|
|
@@ -1252,13 +1307,18 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1252
1307
|
raise NotFoundError(
|
|
1253
1308
|
f"Item {item_id} does not exist inside Collection {collection_id}"
|
|
1254
1309
|
)
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1310
|
+
|
|
1311
|
+
if script_operations:
|
|
1312
|
+
script = operations_to_script(
|
|
1313
|
+
script_operations, create_nest=create_nest
|
|
1314
|
+
)
|
|
1315
|
+
document_index = search_response["hits"]["hits"][0]["_index"]
|
|
1316
|
+
await self.client.update(
|
|
1317
|
+
index=document_index,
|
|
1318
|
+
id=mk_item_id(item_id, collection_id),
|
|
1319
|
+
body={"script": script},
|
|
1320
|
+
refresh=True,
|
|
1321
|
+
)
|
|
1262
1322
|
except exceptions.NotFoundError:
|
|
1263
1323
|
raise NotFoundError(
|
|
1264
1324
|
f"Item {item_id} does not exist inside Collection {collection_id}"
|
|
@@ -1271,24 +1331,9 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1271
1331
|
item = await self.get_one_item(collection_id, item_id)
|
|
1272
1332
|
|
|
1273
1333
|
if new_collection_id:
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
"source": {
|
|
1278
|
-
"index": f"{ITEMS_INDEX_PREFIX}{collection_id}",
|
|
1279
|
-
"query": {"term": {"id": {"value": item_id}}},
|
|
1280
|
-
},
|
|
1281
|
-
"script": {
|
|
1282
|
-
"lang": "painless",
|
|
1283
|
-
"source": (
|
|
1284
|
-
f"""ctx._id = ctx._id.replace('{collection_id}', '{new_collection_id}');"""
|
|
1285
|
-
f"""ctx._source.collection = '{new_collection_id}';"""
|
|
1286
|
-
),
|
|
1287
|
-
},
|
|
1288
|
-
},
|
|
1289
|
-
wait_for_completion=True,
|
|
1290
|
-
refresh=True,
|
|
1291
|
-
)
|
|
1334
|
+
item["collection"] = new_collection_id
|
|
1335
|
+
item = await self.async_prep_create_item(item=item, base_url=base_url)
|
|
1336
|
+
await self.create_item(item=item, refresh=True)
|
|
1292
1337
|
|
|
1293
1338
|
await self.delete_item(
|
|
1294
1339
|
item_id=item_id,
|
|
@@ -1296,7 +1341,6 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1296
1341
|
refresh=refresh,
|
|
1297
1342
|
)
|
|
1298
1343
|
|
|
1299
|
-
item["collection"] = new_collection_id
|
|
1300
1344
|
collection_id = new_collection_id
|
|
1301
1345
|
|
|
1302
1346
|
if new_item_id:
|
|
@@ -1657,6 +1701,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1657
1701
|
)
|
|
1658
1702
|
# Delete the item index for the collection
|
|
1659
1703
|
await delete_item_index(collection_id)
|
|
1704
|
+
await self.async_index_inserter.refresh_cache()
|
|
1660
1705
|
|
|
1661
1706
|
async def bulk_async(
|
|
1662
1707
|
self,
|
{stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/version.py
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.10.0"
|
|
File without changes
|
|
File without changes
|
{stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/__init__.py
RENAMED
|
File without changes
|
{stac_fastapi_opensearch-6.8.1 → stac_fastapi_opensearch-6.10.0}/stac_fastapi/opensearch/config.py
RENAMED
|
File without changes
|