stac-fastapi-opensearch 4.1.0__tar.gz → 4.2.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-4.1.0 → stac_fastapi_opensearch-4.2.0}/PKG-INFO +7 -4
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/README.md +6 -3
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/setup.py +1 -1
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/app.py +35 -15
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/config.py +24 -2
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/database_logic.py +172 -29
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/version.py +1 -1
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi_opensearch.egg-info/PKG-INFO +7 -4
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi_opensearch.egg-info/requires.txt +1 -1
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/setup.cfg +0 -0
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/__init__.py +0 -0
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi_opensearch.egg-info/SOURCES.txt +0 -0
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi_opensearch.egg-info/dependency_links.txt +0 -0
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi_opensearch.egg-info/entry_points.txt +0 -0
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi_opensearch.egg-info/not-zip-safe +0 -0
- {stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi_opensearch.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: stac_fastapi_opensearch
|
|
3
|
-
Version: 4.
|
|
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
|
|
@@ -125,6 +125,7 @@ You can customize additional settings in your `.env` file:
|
|
|
125
125
|
| `STAC_FASTAPI_TITLE` | Title of the API in the documentation. | `stac-fastapi-elasticsearch` or `stac-fastapi-opensearch` | Optional |
|
|
126
126
|
| `STAC_FASTAPI_DESCRIPTION` | Description of the API in the documentation. | N/A | Optional |
|
|
127
127
|
| `STAC_FASTAPI_VERSION` | API version. | `2.1` | Optional |
|
|
128
|
+
| `STAC_FASTAPI_LANDING_PAGE_ID` | Landing page ID | `stac-fastapi` | Optional |
|
|
128
129
|
| `APP_HOST` | Server bind address. | `0.0.0.0` | Optional |
|
|
129
130
|
| `APP_PORT` | Server port. | `8080` | Optional |
|
|
130
131
|
| `ENVIRONMENT` | Runtime environment. | `local` | Optional |
|
|
@@ -132,10 +133,12 @@ You can customize additional settings in your `.env` file:
|
|
|
132
133
|
| `RELOAD` | Enable auto-reload for development. | `true` | Optional |
|
|
133
134
|
| `STAC_FASTAPI_RATE_LIMIT` | API rate limit per client. | `200/minute` | Optional |
|
|
134
135
|
| `BACKEND` | Tests-related variable | `elasticsearch` or `opensearch` based on the backend | Optional |
|
|
135
|
-
| `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional |
|
|
136
|
-
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
|
|
136
|
+
| `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional | |
|
|
137
137
|
| `OPENSEARCH_VERSION` | OpenSearch version | `2.11.1` | Optional
|
|
138
|
-
| `
|
|
138
|
+
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional
|
|
139
|
+
| `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 |
|
|
140
|
+
| `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 |
|
|
141
|
+
| `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 |
|
|
139
142
|
|
|
140
143
|
> [!NOTE]
|
|
141
144
|
> 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.
|
|
@@ -104,6 +104,7 @@ You can customize additional settings in your `.env` file:
|
|
|
104
104
|
| `STAC_FASTAPI_TITLE` | Title of the API in the documentation. | `stac-fastapi-elasticsearch` or `stac-fastapi-opensearch` | Optional |
|
|
105
105
|
| `STAC_FASTAPI_DESCRIPTION` | Description of the API in the documentation. | N/A | Optional |
|
|
106
106
|
| `STAC_FASTAPI_VERSION` | API version. | `2.1` | Optional |
|
|
107
|
+
| `STAC_FASTAPI_LANDING_PAGE_ID` | Landing page ID | `stac-fastapi` | Optional |
|
|
107
108
|
| `APP_HOST` | Server bind address. | `0.0.0.0` | Optional |
|
|
108
109
|
| `APP_PORT` | Server port. | `8080` | Optional |
|
|
109
110
|
| `ENVIRONMENT` | Runtime environment. | `local` | Optional |
|
|
@@ -111,10 +112,12 @@ You can customize additional settings in your `.env` file:
|
|
|
111
112
|
| `RELOAD` | Enable auto-reload for development. | `true` | Optional |
|
|
112
113
|
| `STAC_FASTAPI_RATE_LIMIT` | API rate limit per client. | `200/minute` | Optional |
|
|
113
114
|
| `BACKEND` | Tests-related variable | `elasticsearch` or `opensearch` based on the backend | Optional |
|
|
114
|
-
| `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional |
|
|
115
|
-
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
|
|
115
|
+
| `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional | |
|
|
116
116
|
| `OPENSEARCH_VERSION` | OpenSearch version | `2.11.1` | Optional
|
|
117
|
-
| `
|
|
117
|
+
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional
|
|
118
|
+
| `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 |
|
|
119
|
+
| `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 |
|
|
120
|
+
| `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 |
|
|
118
121
|
|
|
119
122
|
> [!NOTE]
|
|
120
123
|
> 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.
|
{stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/app.py
RENAMED
|
@@ -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.
|
|
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,
|
|
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,
|
{stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/config.py
RENAMED
|
@@ -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
|
-
|
|
539
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1082
|
-
The `mk_actions` function is called to generate a list of actions for the bulk insert.
|
|
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
|
-
|
|
1216
|
+
**kwargs: Any,
|
|
1099
1217
|
) -> Tuple[int, List[Dict[str, Any]]]:
|
|
1100
1218
|
"""
|
|
1101
|
-
Perform a bulk insert of items into the database
|
|
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
|
-
|
|
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.
|
|
1117
|
-
|
|
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,
|
{stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/version.py
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "4.
|
|
2
|
+
__version__ = "4.2.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: stac-fastapi-opensearch
|
|
3
|
-
Version: 4.
|
|
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
|
|
@@ -125,6 +125,7 @@ You can customize additional settings in your `.env` file:
|
|
|
125
125
|
| `STAC_FASTAPI_TITLE` | Title of the API in the documentation. | `stac-fastapi-elasticsearch` or `stac-fastapi-opensearch` | Optional |
|
|
126
126
|
| `STAC_FASTAPI_DESCRIPTION` | Description of the API in the documentation. | N/A | Optional |
|
|
127
127
|
| `STAC_FASTAPI_VERSION` | API version. | `2.1` | Optional |
|
|
128
|
+
| `STAC_FASTAPI_LANDING_PAGE_ID` | Landing page ID | `stac-fastapi` | Optional |
|
|
128
129
|
| `APP_HOST` | Server bind address. | `0.0.0.0` | Optional |
|
|
129
130
|
| `APP_PORT` | Server port. | `8080` | Optional |
|
|
130
131
|
| `ENVIRONMENT` | Runtime environment. | `local` | Optional |
|
|
@@ -132,10 +133,12 @@ You can customize additional settings in your `.env` file:
|
|
|
132
133
|
| `RELOAD` | Enable auto-reload for development. | `true` | Optional |
|
|
133
134
|
| `STAC_FASTAPI_RATE_LIMIT` | API rate limit per client. | `200/minute` | Optional |
|
|
134
135
|
| `BACKEND` | Tests-related variable | `elasticsearch` or `opensearch` based on the backend | Optional |
|
|
135
|
-
| `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional |
|
|
136
|
-
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
|
|
136
|
+
| `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional | |
|
|
137
137
|
| `OPENSEARCH_VERSION` | OpenSearch version | `2.11.1` | Optional
|
|
138
|
-
| `
|
|
138
|
+
| `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional
|
|
139
|
+
| `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 |
|
|
140
|
+
| `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 |
|
|
141
|
+
| `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 |
|
|
139
142
|
|
|
140
143
|
> [!NOTE]
|
|
141
144
|
> 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.
|
|
File without changes
|
{stac_fastapi_opensearch-4.1.0 → stac_fastapi_opensearch-4.2.0}/stac_fastapi/opensearch/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|