stac-fastapi-opensearch 4.1.0__py3-none-any.whl → 5.0.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stac_fastapi/opensearch/app.py +38 -18
- stac_fastapi/opensearch/config.py +24 -1
- stac_fastapi/opensearch/database_logic.py +227 -169
- stac_fastapi/opensearch/version.py +1 -1
- stac_fastapi_opensearch-5.0.0a0.dist-info/METADATA +577 -0
- stac_fastapi_opensearch-5.0.0a0.dist-info/RECORD +10 -0
- stac_fastapi_opensearch-4.1.0.dist-info/METADATA +0 -380
- stac_fastapi_opensearch-4.1.0.dist-info/RECORD +0 -10
- {stac_fastapi_opensearch-4.1.0.dist-info → stac_fastapi_opensearch-5.0.0a0.dist-info}/WHEEL +0 -0
- {stac_fastapi_opensearch-4.1.0.dist-info → stac_fastapi_opensearch-5.0.0a0.dist-info}/entry_points.txt +0 -0
- {stac_fastapi_opensearch-4.1.0.dist-info → stac_fastapi_opensearch-5.0.0a0.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import logging
|
|
6
6
|
from base64 import urlsafe_b64decode, urlsafe_b64encode
|
|
7
7
|
from copy import deepcopy
|
|
8
|
-
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type
|
|
8
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union
|
|
9
9
|
|
|
10
10
|
import attr
|
|
11
11
|
from opensearchpy import exceptions, helpers
|
|
@@ -14,7 +14,30 @@ from opensearchpy.helpers.search import Search
|
|
|
14
14
|
from starlette.requests import Request
|
|
15
15
|
|
|
16
16
|
from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
|
|
17
|
-
from stac_fastapi.core.
|
|
17
|
+
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
|
|
18
|
+
from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon
|
|
19
|
+
from stac_fastapi.opensearch.config import (
|
|
20
|
+
AsyncOpensearchSettings as AsyncSearchSettings,
|
|
21
|
+
)
|
|
22
|
+
from stac_fastapi.opensearch.config import OpensearchSettings as SyncSearchSettings
|
|
23
|
+
from stac_fastapi.sfeos_helpers import filter
|
|
24
|
+
from stac_fastapi.sfeos_helpers.database import (
|
|
25
|
+
apply_free_text_filter_shared,
|
|
26
|
+
apply_intersects_filter_shared,
|
|
27
|
+
create_index_templates_shared,
|
|
28
|
+
delete_item_index_shared,
|
|
29
|
+
get_queryables_mapping_shared,
|
|
30
|
+
index_alias_by_collection_id,
|
|
31
|
+
index_by_collection_id,
|
|
32
|
+
indices,
|
|
33
|
+
mk_actions,
|
|
34
|
+
mk_item_id,
|
|
35
|
+
populate_sort_shared,
|
|
36
|
+
return_date,
|
|
37
|
+
validate_refresh,
|
|
38
|
+
)
|
|
39
|
+
from stac_fastapi.sfeos_helpers.mappings import (
|
|
40
|
+
AGGREGATION_MAPPING,
|
|
18
41
|
COLLECTIONS_INDEX,
|
|
19
42
|
DEFAULT_SORT,
|
|
20
43
|
ES_COLLECTIONS_MAPPINGS,
|
|
@@ -23,20 +46,9 @@ from stac_fastapi.core.database_logic import (
|
|
|
23
46
|
ITEM_INDICES,
|
|
24
47
|
ITEMS_INDEX_PREFIX,
|
|
25
48
|
Geometry,
|
|
26
|
-
index_alias_by_collection_id,
|
|
27
|
-
index_by_collection_id,
|
|
28
|
-
indices,
|
|
29
|
-
mk_actions,
|
|
30
|
-
mk_item_id,
|
|
31
|
-
)
|
|
32
|
-
from stac_fastapi.core.extensions import filter
|
|
33
|
-
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
|
|
34
|
-
from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon
|
|
35
|
-
from stac_fastapi.opensearch.config import (
|
|
36
|
-
AsyncOpensearchSettings as AsyncSearchSettings,
|
|
37
49
|
)
|
|
38
|
-
from stac_fastapi.opensearch.config import OpensearchSettings as SyncSearchSettings
|
|
39
50
|
from stac_fastapi.types.errors import ConflictError, NotFoundError
|
|
51
|
+
from stac_fastapi.types.rfc3339 import DateTimeType
|
|
40
52
|
from stac_fastapi.types.stac import Collection, Item
|
|
41
53
|
|
|
42
54
|
logger = logging.getLogger(__name__)
|
|
@@ -50,23 +62,7 @@ async def create_index_templates() -> None:
|
|
|
50
62
|
None
|
|
51
63
|
|
|
52
64
|
"""
|
|
53
|
-
|
|
54
|
-
await client.indices.put_template(
|
|
55
|
-
name=f"template_{COLLECTIONS_INDEX}",
|
|
56
|
-
body={
|
|
57
|
-
"index_patterns": [f"{COLLECTIONS_INDEX}*"],
|
|
58
|
-
"mappings": ES_COLLECTIONS_MAPPINGS,
|
|
59
|
-
},
|
|
60
|
-
)
|
|
61
|
-
await client.indices.put_template(
|
|
62
|
-
name=f"template_{ITEMS_INDEX_PREFIX}",
|
|
63
|
-
body={
|
|
64
|
-
"index_patterns": [f"{ITEMS_INDEX_PREFIX}*"],
|
|
65
|
-
"settings": ES_ITEMS_SETTINGS,
|
|
66
|
-
"mappings": ES_ITEMS_MAPPINGS,
|
|
67
|
-
},
|
|
68
|
-
)
|
|
69
|
-
await client.close()
|
|
65
|
+
await create_index_templates_shared(settings=AsyncSearchSettings())
|
|
70
66
|
|
|
71
67
|
|
|
72
68
|
async def create_collection_index() -> None:
|
|
@@ -125,18 +121,13 @@ async def delete_item_index(collection_id: str) -> None:
|
|
|
125
121
|
|
|
126
122
|
Args:
|
|
127
123
|
collection_id (str): The ID of the collection whose items index will be deleted.
|
|
128
|
-
"""
|
|
129
|
-
client = AsyncSearchSettings().create_client
|
|
130
124
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
else:
|
|
138
|
-
await client.indices.delete(index=name)
|
|
139
|
-
await client.close()
|
|
125
|
+
Notes:
|
|
126
|
+
This function delegates to the shared implementation in delete_item_index_shared.
|
|
127
|
+
"""
|
|
128
|
+
await delete_item_index_shared(
|
|
129
|
+
settings=AsyncSearchSettings(), collection_id=collection_id
|
|
130
|
+
)
|
|
140
131
|
|
|
141
132
|
|
|
142
133
|
@attr.s
|
|
@@ -161,76 +152,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
161
152
|
|
|
162
153
|
extensions: List[str] = attr.ib(default=attr.Factory(list))
|
|
163
154
|
|
|
164
|
-
aggregation_mapping: Dict[str, Dict[str, Any]] =
|
|
165
|
-
"total_count": {"value_count": {"field": "id"}},
|
|
166
|
-
"collection_frequency": {"terms": {"field": "collection", "size": 100}},
|
|
167
|
-
"platform_frequency": {"terms": {"field": "properties.platform", "size": 100}},
|
|
168
|
-
"cloud_cover_frequency": {
|
|
169
|
-
"range": {
|
|
170
|
-
"field": "properties.eo:cloud_cover",
|
|
171
|
-
"ranges": [
|
|
172
|
-
{"to": 5},
|
|
173
|
-
{"from": 5, "to": 15},
|
|
174
|
-
{"from": 15, "to": 40},
|
|
175
|
-
{"from": 40},
|
|
176
|
-
],
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
"datetime_frequency": {
|
|
180
|
-
"date_histogram": {
|
|
181
|
-
"field": "properties.datetime",
|
|
182
|
-
"calendar_interval": "month",
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
"datetime_min": {"min": {"field": "properties.datetime"}},
|
|
186
|
-
"datetime_max": {"max": {"field": "properties.datetime"}},
|
|
187
|
-
"grid_code_frequency": {
|
|
188
|
-
"terms": {
|
|
189
|
-
"field": "properties.grid:code",
|
|
190
|
-
"missing": "none",
|
|
191
|
-
"size": 10000,
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
"sun_elevation_frequency": {
|
|
195
|
-
"histogram": {"field": "properties.view:sun_elevation", "interval": 5}
|
|
196
|
-
},
|
|
197
|
-
"sun_azimuth_frequency": {
|
|
198
|
-
"histogram": {"field": "properties.view:sun_azimuth", "interval": 5}
|
|
199
|
-
},
|
|
200
|
-
"off_nadir_frequency": {
|
|
201
|
-
"histogram": {"field": "properties.view:off_nadir", "interval": 5}
|
|
202
|
-
},
|
|
203
|
-
"centroid_geohash_grid_frequency": {
|
|
204
|
-
"geohash_grid": {
|
|
205
|
-
"field": "properties.proj:centroid",
|
|
206
|
-
"precision": 1,
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
"centroid_geohex_grid_frequency": {
|
|
210
|
-
"geohex_grid": {
|
|
211
|
-
"field": "properties.proj:centroid",
|
|
212
|
-
"precision": 0,
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
"centroid_geotile_grid_frequency": {
|
|
216
|
-
"geotile_grid": {
|
|
217
|
-
"field": "properties.proj:centroid",
|
|
218
|
-
"precision": 0,
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
"geometry_geohash_grid_frequency": {
|
|
222
|
-
"geohash_grid": {
|
|
223
|
-
"field": "geometry",
|
|
224
|
-
"precision": 1,
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
"geometry_geotile_grid_frequency": {
|
|
228
|
-
"geotile_grid": {
|
|
229
|
-
"field": "geometry",
|
|
230
|
-
"precision": 0,
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
}
|
|
155
|
+
aggregation_mapping: Dict[str, Dict[str, Any]] = AGGREGATION_MAPPING
|
|
234
156
|
|
|
235
157
|
"""CORE LOGIC"""
|
|
236
158
|
|
|
@@ -307,6 +229,23 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
307
229
|
)
|
|
308
230
|
return item["_source"]
|
|
309
231
|
|
|
232
|
+
async def get_queryables_mapping(self, collection_id: str = "*") -> dict:
|
|
233
|
+
"""Retrieve mapping of Queryables for search.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
collection_id (str, optional): The id of the Collection the Queryables
|
|
237
|
+
belongs to. Defaults to "*".
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
dict: A dictionary containing the Queryables mappings.
|
|
241
|
+
"""
|
|
242
|
+
mappings = await self.client.indices.get_mapping(
|
|
243
|
+
index=f"{ITEMS_INDEX_PREFIX}{collection_id}",
|
|
244
|
+
)
|
|
245
|
+
return await get_queryables_mapping_shared(
|
|
246
|
+
collection_id=collection_id, mappings=mappings
|
|
247
|
+
)
|
|
248
|
+
|
|
310
249
|
@staticmethod
|
|
311
250
|
def make_search():
|
|
312
251
|
"""Database logic to create a Search instance."""
|
|
@@ -324,27 +263,37 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
324
263
|
|
|
325
264
|
@staticmethod
|
|
326
265
|
def apply_free_text_filter(search: Search, free_text_queries: Optional[List[str]]):
|
|
327
|
-
"""
|
|
328
|
-
if free_text_queries is not None:
|
|
329
|
-
free_text_query_string = '" OR properties.\\*:"'.join(free_text_queries)
|
|
330
|
-
search = search.query(
|
|
331
|
-
"query_string", query=f'properties.\\*:"{free_text_query_string}"'
|
|
332
|
-
)
|
|
266
|
+
"""Create a free text query for OpenSearch queries.
|
|
333
267
|
|
|
334
|
-
|
|
268
|
+
This method delegates to the shared implementation in apply_free_text_filter_shared.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
search (Search): The search object to apply the query to.
|
|
272
|
+
free_text_queries (Optional[List[str]]): A list of text strings to search for in the properties.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Search: The search object with the free text query applied, or the original search
|
|
276
|
+
object if no free_text_queries were provided.
|
|
277
|
+
"""
|
|
278
|
+
return apply_free_text_filter_shared(
|
|
279
|
+
search=search, free_text_queries=free_text_queries
|
|
280
|
+
)
|
|
335
281
|
|
|
336
282
|
@staticmethod
|
|
337
|
-
def apply_datetime_filter(
|
|
283
|
+
def apply_datetime_filter(
|
|
284
|
+
search: Search, interval: Optional[Union[DateTimeType, str]]
|
|
285
|
+
):
|
|
338
286
|
"""Apply a filter to search based on datetime field, start_datetime, and end_datetime fields.
|
|
339
287
|
|
|
340
288
|
Args:
|
|
341
289
|
search (Search): The search object to filter.
|
|
342
|
-
|
|
290
|
+
interval: Optional[Union[DateTimeType, str]]
|
|
343
291
|
|
|
344
292
|
Returns:
|
|
345
293
|
Search: The filtered search object.
|
|
346
294
|
"""
|
|
347
295
|
should = []
|
|
296
|
+
datetime_search = return_date(interval)
|
|
348
297
|
|
|
349
298
|
# If the request is a single datetime return
|
|
350
299
|
# items with datetimes equal to the requested datetime OR
|
|
@@ -497,21 +446,8 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
497
446
|
Notes:
|
|
498
447
|
A geo_shape filter is added to the search object, set to intersect with the specified geometry.
|
|
499
448
|
"""
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
{
|
|
503
|
-
"geo_shape": {
|
|
504
|
-
"geometry": {
|
|
505
|
-
"shape": {
|
|
506
|
-
"type": intersects.type.lower(),
|
|
507
|
-
"coordinates": intersects.coordinates,
|
|
508
|
-
},
|
|
509
|
-
"relation": "intersects",
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
)
|
|
514
|
-
)
|
|
449
|
+
filter = apply_intersects_filter_shared(intersects=intersects)
|
|
450
|
+
return search.filter(Q(filter))
|
|
515
451
|
|
|
516
452
|
@staticmethod
|
|
517
453
|
def apply_stacql_filter(search: Search, op: str, field: str, value: float):
|
|
@@ -535,8 +471,9 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
535
471
|
|
|
536
472
|
return search
|
|
537
473
|
|
|
538
|
-
|
|
539
|
-
|
|
474
|
+
async def apply_cql2_filter(
|
|
475
|
+
self, search: Search, _filter: Optional[Dict[str, Any]]
|
|
476
|
+
):
|
|
540
477
|
"""
|
|
541
478
|
Apply a CQL2 filter to an Opensearch Search object.
|
|
542
479
|
|
|
@@ -556,18 +493,25 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
556
493
|
otherwise the original Search object.
|
|
557
494
|
"""
|
|
558
495
|
if _filter is not None:
|
|
559
|
-
es_query = filter.to_es(_filter)
|
|
496
|
+
es_query = filter.to_es(await self.get_queryables_mapping(), _filter)
|
|
560
497
|
search = search.filter(es_query)
|
|
561
498
|
|
|
562
499
|
return search
|
|
563
500
|
|
|
564
501
|
@staticmethod
|
|
565
502
|
def populate_sort(sortby: List) -> Optional[Dict[str, Dict[str, str]]]:
|
|
566
|
-
"""
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
503
|
+
"""Create a sort configuration for OpenSearch queries.
|
|
504
|
+
|
|
505
|
+
This method delegates to the shared implementation in populate_sort_shared.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
sortby (List): A list of sort specifications, each containing a field and direction.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
Optional[Dict[str, Dict[str, str]]]: A dictionary mapping field names to sort direction
|
|
512
|
+
configurations, or None if no sort was specified.
|
|
513
|
+
"""
|
|
514
|
+
return populate_sort_shared(sortby=sortby)
|
|
571
515
|
|
|
572
516
|
async def execute_search(
|
|
573
517
|
self,
|
|
@@ -864,15 +808,17 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
864
808
|
async def create_item(
|
|
865
809
|
self,
|
|
866
810
|
item: Item,
|
|
867
|
-
refresh: bool = False,
|
|
868
811
|
base_url: str = "",
|
|
869
812
|
exist_ok: bool = False,
|
|
813
|
+
**kwargs: Any,
|
|
870
814
|
):
|
|
871
815
|
"""Database logic for creating one item.
|
|
872
816
|
|
|
873
817
|
Args:
|
|
874
818
|
item (Item): The item to be created.
|
|
875
|
-
|
|
819
|
+
base_url (str, optional): The base URL for the item. Defaults to an empty string.
|
|
820
|
+
exist_ok (bool, optional): Whether to allow the item to exist already. Defaults to False.
|
|
821
|
+
**kwargs: Additional keyword arguments like refresh.
|
|
876
822
|
|
|
877
823
|
Raises:
|
|
878
824
|
ConflictError: If the item already exists in the database.
|
|
@@ -883,6 +829,19 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
883
829
|
# todo: check if collection exists, but cache
|
|
884
830
|
item_id = item["id"]
|
|
885
831
|
collection_id = item["collection"]
|
|
832
|
+
|
|
833
|
+
# Ensure kwargs is a dictionary
|
|
834
|
+
kwargs = kwargs or {}
|
|
835
|
+
|
|
836
|
+
# Resolve the `refresh` parameter
|
|
837
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
838
|
+
refresh = validate_refresh(refresh)
|
|
839
|
+
|
|
840
|
+
# Log the creation attempt
|
|
841
|
+
logger.info(
|
|
842
|
+
f"Creating item {item_id} in collection {collection_id} with refresh={refresh}"
|
|
843
|
+
)
|
|
844
|
+
|
|
886
845
|
item = await self.async_prep_create_item(
|
|
887
846
|
item=item, base_url=base_url, exist_ok=exist_ok
|
|
888
847
|
)
|
|
@@ -893,19 +852,29 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
893
852
|
refresh=refresh,
|
|
894
853
|
)
|
|
895
854
|
|
|
896
|
-
async def delete_item(
|
|
897
|
-
self, item_id: str, collection_id: str, refresh: bool = False
|
|
898
|
-
):
|
|
855
|
+
async def delete_item(self, item_id: str, collection_id: str, **kwargs: Any):
|
|
899
856
|
"""Delete a single item from the database.
|
|
900
857
|
|
|
901
858
|
Args:
|
|
902
859
|
item_id (str): The id of the Item to be deleted.
|
|
903
860
|
collection_id (str): The id of the Collection that the Item belongs to.
|
|
904
|
-
|
|
861
|
+
**kwargs: Additional keyword arguments like refresh.
|
|
905
862
|
|
|
906
863
|
Raises:
|
|
907
864
|
NotFoundError: If the Item does not exist in the database.
|
|
908
865
|
"""
|
|
866
|
+
# Ensure kwargs is a dictionary
|
|
867
|
+
kwargs = kwargs or {}
|
|
868
|
+
|
|
869
|
+
# Resolve the `refresh` parameter
|
|
870
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
871
|
+
refresh = validate_refresh(refresh)
|
|
872
|
+
|
|
873
|
+
# Log the deletion attempt
|
|
874
|
+
logger.info(
|
|
875
|
+
f"Deleting item {item_id} from collection {collection_id} with refresh={refresh}"
|
|
876
|
+
)
|
|
877
|
+
|
|
909
878
|
try:
|
|
910
879
|
await self.client.delete(
|
|
911
880
|
index=index_alias_by_collection_id(collection_id),
|
|
@@ -935,12 +904,12 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
935
904
|
except exceptions.NotFoundError:
|
|
936
905
|
raise NotFoundError(f"Mapping for index {index_name} not found")
|
|
937
906
|
|
|
938
|
-
async def create_collection(self, collection: Collection,
|
|
907
|
+
async def create_collection(self, collection: Collection, **kwargs: Any):
|
|
939
908
|
"""Create a single collection in the database.
|
|
940
909
|
|
|
941
910
|
Args:
|
|
942
911
|
collection (Collection): The Collection object to be created.
|
|
943
|
-
|
|
912
|
+
**kwargs: Additional keyword arguments like refresh.
|
|
944
913
|
|
|
945
914
|
Raises:
|
|
946
915
|
ConflictError: If a Collection with the same id already exists in the database.
|
|
@@ -950,6 +919,16 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
950
919
|
"""
|
|
951
920
|
collection_id = collection["id"]
|
|
952
921
|
|
|
922
|
+
# Ensure kwargs is a dictionary
|
|
923
|
+
kwargs = kwargs or {}
|
|
924
|
+
|
|
925
|
+
# Resolve the `refresh` parameter
|
|
926
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
927
|
+
refresh = validate_refresh(refresh)
|
|
928
|
+
|
|
929
|
+
# Log the creation attempt
|
|
930
|
+
logger.info(f"Creating collection {collection_id} with refresh={refresh}")
|
|
931
|
+
|
|
953
932
|
if await self.client.exists(index=COLLECTIONS_INDEX, id=collection_id):
|
|
954
933
|
raise ConflictError(f"Collection {collection_id} already exists")
|
|
955
934
|
|
|
@@ -989,14 +968,14 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
989
968
|
return collection["_source"]
|
|
990
969
|
|
|
991
970
|
async def update_collection(
|
|
992
|
-
self, collection_id: str, collection: Collection,
|
|
971
|
+
self, collection_id: str, collection: Collection, **kwargs: Any
|
|
993
972
|
):
|
|
994
973
|
"""Update a collection from the database.
|
|
995
974
|
|
|
996
975
|
Args:
|
|
997
|
-
self: The instance of the object calling this function.
|
|
998
976
|
collection_id (str): The ID of the collection to be updated.
|
|
999
977
|
collection (Collection): The Collection object to be used for the update.
|
|
978
|
+
**kwargs: Additional keyword arguments like refresh.
|
|
1000
979
|
|
|
1001
980
|
Raises:
|
|
1002
981
|
NotFoundError: If the collection with the given `collection_id` is not
|
|
@@ -1007,9 +986,23 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1007
986
|
`collection_id` and with the collection specified in the `Collection` object.
|
|
1008
987
|
If the collection is not found, a `NotFoundError` is raised.
|
|
1009
988
|
"""
|
|
989
|
+
# Ensure kwargs is a dictionary
|
|
990
|
+
kwargs = kwargs or {}
|
|
991
|
+
|
|
992
|
+
# Resolve the `refresh` parameter
|
|
993
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
994
|
+
refresh = validate_refresh(refresh)
|
|
995
|
+
|
|
996
|
+
# Log the update attempt
|
|
997
|
+
logger.info(f"Updating collection {collection_id} with refresh={refresh}")
|
|
998
|
+
|
|
1010
999
|
await self.find_collection(collection_id=collection_id)
|
|
1011
1000
|
|
|
1012
1001
|
if collection_id != collection["id"]:
|
|
1002
|
+
logger.info(
|
|
1003
|
+
f"Collection ID change detected: {collection_id} -> {collection['id']}"
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1013
1006
|
await self.create_collection(collection, refresh=refresh)
|
|
1014
1007
|
|
|
1015
1008
|
await self.client.reindex(
|
|
@@ -1025,7 +1018,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1025
1018
|
refresh=refresh,
|
|
1026
1019
|
)
|
|
1027
1020
|
|
|
1028
|
-
await self.delete_collection(collection_id)
|
|
1021
|
+
await self.delete_collection(collection_id=collection_id, **kwargs)
|
|
1029
1022
|
|
|
1030
1023
|
else:
|
|
1031
1024
|
await self.client.index(
|
|
@@ -1035,23 +1028,34 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1035
1028
|
refresh=refresh,
|
|
1036
1029
|
)
|
|
1037
1030
|
|
|
1038
|
-
async def delete_collection(self, collection_id: str,
|
|
1031
|
+
async def delete_collection(self, collection_id: str, **kwargs: Any):
|
|
1039
1032
|
"""Delete a collection from the database.
|
|
1040
1033
|
|
|
1041
1034
|
Parameters:
|
|
1042
1035
|
self: The instance of the object calling this function.
|
|
1043
1036
|
collection_id (str): The ID of the collection to be deleted.
|
|
1044
|
-
|
|
1037
|
+
**kwargs: Additional keyword arguments like refresh.
|
|
1045
1038
|
|
|
1046
1039
|
Raises:
|
|
1047
1040
|
NotFoundError: If the collection with the given `collection_id` is not found in the database.
|
|
1048
1041
|
|
|
1049
1042
|
Notes:
|
|
1050
1043
|
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.
|
|
1044
|
+
deletes the collection. If `refresh` is set to "true", "false", or "wait_for", the index is refreshed accordingly after
|
|
1045
|
+
the deletion. Additionally, this function also calls `delete_item_index` to delete the index for the items in the collection.
|
|
1053
1046
|
"""
|
|
1047
|
+
# Ensure kwargs is a dictionary
|
|
1048
|
+
kwargs = kwargs or {}
|
|
1049
|
+
|
|
1054
1050
|
await self.find_collection(collection_id=collection_id)
|
|
1051
|
+
|
|
1052
|
+
# Resolve the `refresh` parameter
|
|
1053
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
1054
|
+
refresh = validate_refresh(refresh)
|
|
1055
|
+
|
|
1056
|
+
# Log the deletion attempt
|
|
1057
|
+
logger.info(f"Deleting collection {collection_id} with refresh={refresh}")
|
|
1058
|
+
|
|
1055
1059
|
await self.client.delete(
|
|
1056
1060
|
index=COLLECTIONS_INDEX, id=collection_id, refresh=refresh
|
|
1057
1061
|
)
|
|
@@ -1061,7 +1065,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1061
1065
|
self,
|
|
1062
1066
|
collection_id: str,
|
|
1063
1067
|
processed_items: List[Item],
|
|
1064
|
-
|
|
1068
|
+
**kwargs: Any,
|
|
1065
1069
|
) -> Tuple[int, List[Dict[str, Any]]]:
|
|
1066
1070
|
"""
|
|
1067
1071
|
Perform a bulk insert of items into the database asynchronously.
|
|
@@ -1069,7 +1073,12 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1069
1073
|
Args:
|
|
1070
1074
|
collection_id (str): The ID of the collection to which the items belong.
|
|
1071
1075
|
processed_items (List[Item]): A list of `Item` objects to be inserted into the database.
|
|
1072
|
-
|
|
1076
|
+
**kwargs (Any): Additional keyword arguments, including:
|
|
1077
|
+
- refresh (str, optional): Whether to refresh the index after the bulk insert.
|
|
1078
|
+
Can be "true", "false", or "wait_for". Defaults to the value of `self.sync_settings.database_refresh`.
|
|
1079
|
+
- refresh (bool, optional): Whether to refresh the index after the bulk insert.
|
|
1080
|
+
- raise_on_error (bool, optional): Whether to raise an error if any of the bulk operations fail.
|
|
1081
|
+
Defaults to the value of `self.async_settings.raise_on_bulk_error`.
|
|
1073
1082
|
|
|
1074
1083
|
Returns:
|
|
1075
1084
|
Tuple[int, List[Dict[str, Any]]]: A tuple containing:
|
|
@@ -1078,10 +1087,30 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1078
1087
|
|
|
1079
1088
|
Notes:
|
|
1080
1089
|
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
|
|
1090
|
+
The insert is performed synchronously and blocking, meaning that the function does not return until the insert has
|
|
1091
|
+
completed. The `mk_actions` function is called to generate a list of actions for the bulk insert. The `refresh`
|
|
1092
|
+
parameter determines whether the index is refreshed after the bulk insert:
|
|
1093
|
+
- "true": Forces an immediate refresh of the index.
|
|
1094
|
+
- "false": Does not refresh the index immediately (default behavior).
|
|
1095
|
+
- "wait_for": Waits for the next refresh cycle to make the changes visible.
|
|
1084
1096
|
"""
|
|
1097
|
+
# Ensure kwargs is a dictionary
|
|
1098
|
+
kwargs = kwargs or {}
|
|
1099
|
+
|
|
1100
|
+
# Resolve the `refresh` parameter
|
|
1101
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
1102
|
+
refresh = validate_refresh(refresh)
|
|
1103
|
+
|
|
1104
|
+
# Log the bulk insert attempt
|
|
1105
|
+
logger.info(
|
|
1106
|
+
f"Performing bulk insert for collection {collection_id} with refresh={refresh}"
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
# Handle empty processed_items
|
|
1110
|
+
if not processed_items:
|
|
1111
|
+
logger.warning(f"No items to insert for collection {collection_id}")
|
|
1112
|
+
return 0, []
|
|
1113
|
+
|
|
1085
1114
|
raise_on_error = self.async_settings.raise_on_bulk_error
|
|
1086
1115
|
success, errors = await helpers.async_bulk(
|
|
1087
1116
|
self.client,
|
|
@@ -1089,21 +1118,30 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1089
1118
|
refresh=refresh,
|
|
1090
1119
|
raise_on_error=raise_on_error,
|
|
1091
1120
|
)
|
|
1121
|
+
# Log the result
|
|
1122
|
+
logger.info(
|
|
1123
|
+
f"Bulk insert completed for collection {collection_id}: {success} successes, {len(errors)} errors"
|
|
1124
|
+
)
|
|
1092
1125
|
return success, errors
|
|
1093
1126
|
|
|
1094
1127
|
def bulk_sync(
|
|
1095
1128
|
self,
|
|
1096
1129
|
collection_id: str,
|
|
1097
1130
|
processed_items: List[Item],
|
|
1098
|
-
|
|
1131
|
+
**kwargs: Any,
|
|
1099
1132
|
) -> Tuple[int, List[Dict[str, Any]]]:
|
|
1100
1133
|
"""
|
|
1101
|
-
Perform a bulk insert of items into the database
|
|
1134
|
+
Perform a bulk insert of items into the database asynchronously.
|
|
1102
1135
|
|
|
1103
1136
|
Args:
|
|
1104
1137
|
collection_id (str): The ID of the collection to which the items belong.
|
|
1105
1138
|
processed_items (List[Item]): A list of `Item` objects to be inserted into the database.
|
|
1106
|
-
|
|
1139
|
+
**kwargs (Any): Additional keyword arguments, including:
|
|
1140
|
+
- refresh (str, optional): Whether to refresh the index after the bulk insert.
|
|
1141
|
+
Can be "true", "false", or "wait_for". Defaults to the value of `self.sync_settings.database_refresh`.
|
|
1142
|
+
- refresh (bool, optional): Whether to refresh the index after the bulk insert.
|
|
1143
|
+
- raise_on_error (bool, optional): Whether to raise an error if any of the bulk operations fail.
|
|
1144
|
+
Defaults to the value of `self.async_settings.raise_on_bulk_error`.
|
|
1107
1145
|
|
|
1108
1146
|
Returns:
|
|
1109
1147
|
Tuple[int, List[Dict[str, Any]]]: A tuple containing:
|
|
@@ -1113,9 +1151,29 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1113
1151
|
Notes:
|
|
1114
1152
|
This function performs a bulk insert of `processed_items` into the database using the specified `collection_id`.
|
|
1115
1153
|
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
|
-
|
|
1154
|
+
completed. The `mk_actions` function is called to generate a list of actions for the bulk insert. The `refresh`
|
|
1155
|
+
parameter determines whether the index is refreshed after the bulk insert:
|
|
1156
|
+
- "true": Forces an immediate refresh of the index.
|
|
1157
|
+
- "false": Does not refresh the index immediately (default behavior).
|
|
1158
|
+
- "wait_for": Waits for the next refresh cycle to make the changes visible.
|
|
1118
1159
|
"""
|
|
1160
|
+
# Ensure kwargs is a dictionary
|
|
1161
|
+
kwargs = kwargs or {}
|
|
1162
|
+
|
|
1163
|
+
# Resolve the `refresh` parameter
|
|
1164
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
1165
|
+
refresh = validate_refresh(refresh)
|
|
1166
|
+
|
|
1167
|
+
# Log the bulk insert attempt
|
|
1168
|
+
logger.info(
|
|
1169
|
+
f"Performing bulk insert for collection {collection_id} with refresh={refresh}"
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
# Handle empty processed_items
|
|
1173
|
+
if not processed_items:
|
|
1174
|
+
logger.warning(f"No items to insert for collection {collection_id}")
|
|
1175
|
+
return 0, []
|
|
1176
|
+
|
|
1119
1177
|
raise_on_error = self.sync_settings.raise_on_bulk_error
|
|
1120
1178
|
success, errors = helpers.bulk(
|
|
1121
1179
|
self.sync_client,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "
|
|
2
|
+
__version__ = "5.0.0a0"
|