stac-fastapi-elasticsearch 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/elasticsearch/app.py +37 -18
- stac_fastapi/elasticsearch/config.py +24 -1
- stac_fastapi/elasticsearch/database_logic.py +286 -176
- stac_fastapi/elasticsearch/version.py +1 -1
- stac_fastapi_elasticsearch-5.0.0a0.dist-info/METADATA +576 -0
- stac_fastapi_elasticsearch-5.0.0a0.dist-info/RECORD +10 -0
- stac_fastapi_elasticsearch-4.1.0.dist-info/METADATA +0 -379
- stac_fastapi_elasticsearch-4.1.0.dist-info/RECORD +0 -10
- {stac_fastapi_elasticsearch-4.1.0.dist-info → stac_fastapi_elasticsearch-5.0.0a0.dist-info}/WHEEL +0 -0
- {stac_fastapi_elasticsearch-4.1.0.dist-info → stac_fastapi_elasticsearch-5.0.0a0.dist-info}/entry_points.txt +0 -0
- {stac_fastapi_elasticsearch-4.1.0.dist-info → stac_fastapi_elasticsearch-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
|
import elasticsearch.helpers as helpers
|
|
@@ -14,29 +14,38 @@ from elasticsearch.exceptions import NotFoundError as ESNotFoundError
|
|
|
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.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
|
|
18
|
+
from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon
|
|
19
|
+
from stac_fastapi.elasticsearch.config import AsyncElasticsearchSettings
|
|
20
|
+
from stac_fastapi.elasticsearch.config import (
|
|
21
|
+
ElasticsearchSettings as SyncElasticsearchSettings,
|
|
22
|
+
)
|
|
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,
|
|
26
30
|
index_alias_by_collection_id,
|
|
27
31
|
index_by_collection_id,
|
|
28
32
|
indices,
|
|
29
33
|
mk_actions,
|
|
30
34
|
mk_item_id,
|
|
35
|
+
populate_sort_shared,
|
|
36
|
+
return_date,
|
|
37
|
+
validate_refresh,
|
|
31
38
|
)
|
|
32
|
-
from stac_fastapi.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
from stac_fastapi.sfeos_helpers.mappings import (
|
|
40
|
+
AGGREGATION_MAPPING,
|
|
41
|
+
COLLECTIONS_INDEX,
|
|
42
|
+
DEFAULT_SORT,
|
|
43
|
+
ITEM_INDICES,
|
|
44
|
+
ITEMS_INDEX_PREFIX,
|
|
45
|
+
Geometry,
|
|
38
46
|
)
|
|
39
47
|
from stac_fastapi.types.errors import ConflictError, NotFoundError
|
|
48
|
+
from stac_fastapi.types.rfc3339 import DateTimeType
|
|
40
49
|
from stac_fastapi.types.stac import Collection, Item
|
|
41
50
|
|
|
42
51
|
logger = logging.getLogger(__name__)
|
|
@@ -50,22 +59,7 @@ async def create_index_templates() -> None:
|
|
|
50
59
|
None
|
|
51
60
|
|
|
52
61
|
"""
|
|
53
|
-
|
|
54
|
-
await client.indices.put_index_template(
|
|
55
|
-
name=f"template_{COLLECTIONS_INDEX}",
|
|
56
|
-
body={
|
|
57
|
-
"index_patterns": [f"{COLLECTIONS_INDEX}*"],
|
|
58
|
-
"template": {"mappings": ES_COLLECTIONS_MAPPINGS},
|
|
59
|
-
},
|
|
60
|
-
)
|
|
61
|
-
await client.indices.put_index_template(
|
|
62
|
-
name=f"template_{ITEMS_INDEX_PREFIX}",
|
|
63
|
-
body={
|
|
64
|
-
"index_patterns": [f"{ITEMS_INDEX_PREFIX}*"],
|
|
65
|
-
"template": {"settings": ES_ITEMS_SETTINGS, "mappings": ES_ITEMS_MAPPINGS},
|
|
66
|
-
},
|
|
67
|
-
)
|
|
68
|
-
await client.close()
|
|
62
|
+
await create_index_templates_shared(settings=AsyncElasticsearchSettings())
|
|
69
63
|
|
|
70
64
|
|
|
71
65
|
async def create_collection_index() -> None:
|
|
@@ -110,18 +104,13 @@ async def delete_item_index(collection_id: str):
|
|
|
110
104
|
|
|
111
105
|
Args:
|
|
112
106
|
collection_id (str): The ID of the collection whose items index will be deleted.
|
|
113
|
-
"""
|
|
114
|
-
client = AsyncElasticsearchSettings().create_client
|
|
115
107
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else:
|
|
123
|
-
await client.indices.delete(index=name)
|
|
124
|
-
await client.close()
|
|
108
|
+
Notes:
|
|
109
|
+
This function delegates to the shared implementation in delete_item_index_shared.
|
|
110
|
+
"""
|
|
111
|
+
await delete_item_index_shared(
|
|
112
|
+
settings=AsyncElasticsearchSettings(), collection_id=collection_id
|
|
113
|
+
)
|
|
125
114
|
|
|
126
115
|
|
|
127
116
|
@attr.s
|
|
@@ -150,76 +139,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
150
139
|
|
|
151
140
|
extensions: List[str] = attr.ib(default=attr.Factory(list))
|
|
152
141
|
|
|
153
|
-
aggregation_mapping: Dict[str, Dict[str, Any]] =
|
|
154
|
-
"total_count": {"value_count": {"field": "id"}},
|
|
155
|
-
"collection_frequency": {"terms": {"field": "collection", "size": 100}},
|
|
156
|
-
"platform_frequency": {"terms": {"field": "properties.platform", "size": 100}},
|
|
157
|
-
"cloud_cover_frequency": {
|
|
158
|
-
"range": {
|
|
159
|
-
"field": "properties.eo:cloud_cover",
|
|
160
|
-
"ranges": [
|
|
161
|
-
{"to": 5},
|
|
162
|
-
{"from": 5, "to": 15},
|
|
163
|
-
{"from": 15, "to": 40},
|
|
164
|
-
{"from": 40},
|
|
165
|
-
],
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
"datetime_frequency": {
|
|
169
|
-
"date_histogram": {
|
|
170
|
-
"field": "properties.datetime",
|
|
171
|
-
"calendar_interval": "month",
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
"datetime_min": {"min": {"field": "properties.datetime"}},
|
|
175
|
-
"datetime_max": {"max": {"field": "properties.datetime"}},
|
|
176
|
-
"grid_code_frequency": {
|
|
177
|
-
"terms": {
|
|
178
|
-
"field": "properties.grid:code",
|
|
179
|
-
"missing": "none",
|
|
180
|
-
"size": 10000,
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
"sun_elevation_frequency": {
|
|
184
|
-
"histogram": {"field": "properties.view:sun_elevation", "interval": 5}
|
|
185
|
-
},
|
|
186
|
-
"sun_azimuth_frequency": {
|
|
187
|
-
"histogram": {"field": "properties.view:sun_azimuth", "interval": 5}
|
|
188
|
-
},
|
|
189
|
-
"off_nadir_frequency": {
|
|
190
|
-
"histogram": {"field": "properties.view:off_nadir", "interval": 5}
|
|
191
|
-
},
|
|
192
|
-
"centroid_geohash_grid_frequency": {
|
|
193
|
-
"geohash_grid": {
|
|
194
|
-
"field": "properties.proj:centroid",
|
|
195
|
-
"precision": 1,
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
"centroid_geohex_grid_frequency": {
|
|
199
|
-
"geohex_grid": {
|
|
200
|
-
"field": "properties.proj:centroid",
|
|
201
|
-
"precision": 0,
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
"centroid_geotile_grid_frequency": {
|
|
205
|
-
"geotile_grid": {
|
|
206
|
-
"field": "properties.proj:centroid",
|
|
207
|
-
"precision": 0,
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
"geometry_geohash_grid_frequency": {
|
|
211
|
-
"geohash_grid": {
|
|
212
|
-
"field": "geometry",
|
|
213
|
-
"precision": 1,
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
"geometry_geotile_grid_frequency": {
|
|
217
|
-
"geotile_grid": {
|
|
218
|
-
"field": "geometry",
|
|
219
|
-
"precision": 0,
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
}
|
|
142
|
+
aggregation_mapping: Dict[str, Dict[str, Any]] = AGGREGATION_MAPPING
|
|
223
143
|
|
|
224
144
|
"""CORE LOGIC"""
|
|
225
145
|
|
|
@@ -290,6 +210,23 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
290
210
|
)
|
|
291
211
|
return item["_source"]
|
|
292
212
|
|
|
213
|
+
async def get_queryables_mapping(self, collection_id: str = "*") -> dict:
|
|
214
|
+
"""Retrieve mapping of Queryables for search.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
collection_id (str, optional): The id of the Collection the Queryables
|
|
218
|
+
belongs to. Defaults to "*".
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
dict: A dictionary containing the Queryables mappings.
|
|
222
|
+
"""
|
|
223
|
+
mappings = await self.client.indices.get_mapping(
|
|
224
|
+
index=f"{ITEMS_INDEX_PREFIX}{collection_id}",
|
|
225
|
+
)
|
|
226
|
+
return await get_queryables_mapping_shared(
|
|
227
|
+
collection_id=collection_id, mappings=mappings
|
|
228
|
+
)
|
|
229
|
+
|
|
293
230
|
@staticmethod
|
|
294
231
|
def make_search():
|
|
295
232
|
"""Database logic to create a Search instance."""
|
|
@@ -306,17 +243,20 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
306
243
|
return search.filter("terms", collection=collection_ids)
|
|
307
244
|
|
|
308
245
|
@staticmethod
|
|
309
|
-
def apply_datetime_filter(
|
|
246
|
+
def apply_datetime_filter(
|
|
247
|
+
search: Search, interval: Optional[Union[DateTimeType, str]]
|
|
248
|
+
):
|
|
310
249
|
"""Apply a filter to search on datetime, start_datetime, and end_datetime fields.
|
|
311
250
|
|
|
312
251
|
Args:
|
|
313
252
|
search (Search): The search object to filter.
|
|
314
|
-
|
|
253
|
+
interval: Optional[Union[DateTimeType, str]]
|
|
315
254
|
|
|
316
255
|
Returns:
|
|
317
256
|
Search: The filtered search object.
|
|
318
257
|
"""
|
|
319
258
|
should = []
|
|
259
|
+
datetime_search = return_date(interval)
|
|
320
260
|
|
|
321
261
|
# If the request is a single datetime return
|
|
322
262
|
# items with datetimes equal to the requested datetime OR
|
|
@@ -469,21 +409,8 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
469
409
|
Notes:
|
|
470
410
|
A geo_shape filter is added to the search object, set to intersect with the specified geometry.
|
|
471
411
|
"""
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
{
|
|
475
|
-
"geo_shape": {
|
|
476
|
-
"geometry": {
|
|
477
|
-
"shape": {
|
|
478
|
-
"type": intersects.type.lower(),
|
|
479
|
-
"coordinates": intersects.coordinates,
|
|
480
|
-
},
|
|
481
|
-
"relation": "intersects",
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
)
|
|
486
|
-
)
|
|
412
|
+
filter = apply_intersects_filter_shared(intersects=intersects)
|
|
413
|
+
return search.filter(Q(filter))
|
|
487
414
|
|
|
488
415
|
@staticmethod
|
|
489
416
|
def apply_stacql_filter(search: Search, op: str, field: str, value: float):
|
|
@@ -509,17 +436,25 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
509
436
|
|
|
510
437
|
@staticmethod
|
|
511
438
|
def apply_free_text_filter(search: Search, free_text_queries: Optional[List[str]]):
|
|
512
|
-
"""
|
|
513
|
-
if free_text_queries is not None:
|
|
514
|
-
free_text_query_string = '" OR properties.\\*:"'.join(free_text_queries)
|
|
515
|
-
search = search.query(
|
|
516
|
-
"query_string", query=f'properties.\\*:"{free_text_query_string}"'
|
|
517
|
-
)
|
|
439
|
+
"""Create a free text query for Elasticsearch queries.
|
|
518
440
|
|
|
519
|
-
|
|
441
|
+
This method delegates to the shared implementation in apply_free_text_filter_shared.
|
|
520
442
|
|
|
521
|
-
|
|
522
|
-
|
|
443
|
+
Args:
|
|
444
|
+
search (Search): The search object to apply the query to.
|
|
445
|
+
free_text_queries (Optional[List[str]]): A list of text strings to search for in the properties.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Search: The search object with the free text query applied, or the original search
|
|
449
|
+
object if no free_text_queries were provided.
|
|
450
|
+
"""
|
|
451
|
+
return apply_free_text_filter_shared(
|
|
452
|
+
search=search, free_text_queries=free_text_queries
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
async def apply_cql2_filter(
|
|
456
|
+
self, search: Search, _filter: Optional[Dict[str, Any]]
|
|
457
|
+
):
|
|
523
458
|
"""
|
|
524
459
|
Apply a CQL2 filter to an Elasticsearch Search object.
|
|
525
460
|
|
|
@@ -539,18 +474,25 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
539
474
|
otherwise the original Search object.
|
|
540
475
|
"""
|
|
541
476
|
if _filter is not None:
|
|
542
|
-
es_query = filter.to_es(_filter)
|
|
477
|
+
es_query = filter.to_es(await self.get_queryables_mapping(), _filter)
|
|
543
478
|
search = search.query(es_query)
|
|
544
479
|
|
|
545
480
|
return search
|
|
546
481
|
|
|
547
482
|
@staticmethod
|
|
548
483
|
def populate_sort(sortby: List) -> Optional[Dict[str, Dict[str, str]]]:
|
|
549
|
-
"""
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
484
|
+
"""Create a sort configuration for Elasticsearch queries.
|
|
485
|
+
|
|
486
|
+
This method delegates to the shared implementation in populate_sort_shared.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
sortby (List): A list of sort specifications, each containing a field and direction.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
Optional[Dict[str, Dict[str, str]]]: A dictionary mapping field names to sort direction
|
|
493
|
+
configurations, or None if no sort was specified.
|
|
494
|
+
"""
|
|
495
|
+
return populate_sort_shared(sortby=sortby)
|
|
554
496
|
|
|
555
497
|
async def execute_search(
|
|
556
498
|
self,
|
|
@@ -845,15 +787,19 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
845
787
|
async def create_item(
|
|
846
788
|
self,
|
|
847
789
|
item: Item,
|
|
848
|
-
refresh: bool = False,
|
|
849
790
|
base_url: str = "",
|
|
850
791
|
exist_ok: bool = False,
|
|
792
|
+
**kwargs: Any,
|
|
851
793
|
):
|
|
852
794
|
"""Database logic for creating one item.
|
|
853
795
|
|
|
854
796
|
Args:
|
|
855
797
|
item (Item): The item to be created.
|
|
856
|
-
|
|
798
|
+
base_url (str, optional): The base URL for the item. Defaults to an empty string.
|
|
799
|
+
exist_ok (bool, optional): Whether to allow the item to exist already. Defaults to False.
|
|
800
|
+
**kwargs: Additional keyword arguments.
|
|
801
|
+
- refresh (str): Whether to refresh the index after the operation. Can be "true", "false", or "wait_for".
|
|
802
|
+
- refresh (bool): Whether to refresh the index after the operation. Defaults to the value in `self.async_settings.database_refresh`.
|
|
857
803
|
|
|
858
804
|
Raises:
|
|
859
805
|
ConflictError: If the item already exists in the database.
|
|
@@ -861,12 +807,28 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
861
807
|
Returns:
|
|
862
808
|
None
|
|
863
809
|
"""
|
|
864
|
-
#
|
|
810
|
+
# Extract item and collection IDs
|
|
865
811
|
item_id = item["id"]
|
|
866
812
|
collection_id = item["collection"]
|
|
813
|
+
|
|
814
|
+
# Ensure kwargs is a dictionary
|
|
815
|
+
kwargs = kwargs or {}
|
|
816
|
+
|
|
817
|
+
# Resolve the `refresh` parameter
|
|
818
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
819
|
+
refresh = validate_refresh(refresh)
|
|
820
|
+
|
|
821
|
+
# Log the creation attempt
|
|
822
|
+
logger.info(
|
|
823
|
+
f"Creating item {item_id} in collection {collection_id} with refresh={refresh}"
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
# Prepare the item for insertion
|
|
867
827
|
item = await self.async_prep_create_item(
|
|
868
828
|
item=item, base_url=base_url, exist_ok=exist_ok
|
|
869
829
|
)
|
|
830
|
+
|
|
831
|
+
# Index the item in the database
|
|
870
832
|
await self.client.index(
|
|
871
833
|
index=index_alias_by_collection_id(collection_id),
|
|
872
834
|
id=mk_item_id(item_id, collection_id),
|
|
@@ -874,26 +836,43 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
874
836
|
refresh=refresh,
|
|
875
837
|
)
|
|
876
838
|
|
|
877
|
-
async def delete_item(
|
|
878
|
-
self, item_id: str, collection_id: str, refresh: bool = False
|
|
879
|
-
):
|
|
839
|
+
async def delete_item(self, item_id: str, collection_id: str, **kwargs: Any):
|
|
880
840
|
"""Delete a single item from the database.
|
|
881
841
|
|
|
882
842
|
Args:
|
|
883
843
|
item_id (str): The id of the Item to be deleted.
|
|
884
844
|
collection_id (str): The id of the Collection that the Item belongs to.
|
|
885
|
-
|
|
845
|
+
**kwargs: Additional keyword arguments.
|
|
846
|
+
- refresh (str): Whether to refresh the index after the operation. Can be "true", "false", or "wait_for".
|
|
847
|
+
- refresh (bool): Whether to refresh the index after the operation. Defaults to the value in `self.async_settings.database_refresh`.
|
|
886
848
|
|
|
887
849
|
Raises:
|
|
888
850
|
NotFoundError: If the Item does not exist in the database.
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
None
|
|
889
854
|
"""
|
|
855
|
+
# Ensure kwargs is a dictionary
|
|
856
|
+
kwargs = kwargs or {}
|
|
857
|
+
|
|
858
|
+
# Resolve the `refresh` parameter
|
|
859
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
860
|
+
refresh = validate_refresh(refresh)
|
|
861
|
+
|
|
862
|
+
# Log the deletion attempt
|
|
863
|
+
logger.info(
|
|
864
|
+
f"Deleting item {item_id} from collection {collection_id} with refresh={refresh}"
|
|
865
|
+
)
|
|
866
|
+
|
|
890
867
|
try:
|
|
868
|
+
# Perform the delete operation
|
|
891
869
|
await self.client.delete(
|
|
892
870
|
index=index_alias_by_collection_id(collection_id),
|
|
893
871
|
id=mk_item_id(item_id, collection_id),
|
|
894
872
|
refresh=refresh,
|
|
895
873
|
)
|
|
896
874
|
except ESNotFoundError:
|
|
875
|
+
# Raise a custom NotFoundError if the item does not exist
|
|
897
876
|
raise NotFoundError(
|
|
898
877
|
f"Item {item_id} in collection {collection_id} not found"
|
|
899
878
|
)
|
|
@@ -916,24 +895,41 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
916
895
|
except ESNotFoundError:
|
|
917
896
|
raise NotFoundError(f"Mapping for index {index_name} not found")
|
|
918
897
|
|
|
919
|
-
async def create_collection(self, collection: Collection,
|
|
898
|
+
async def create_collection(self, collection: Collection, **kwargs: Any):
|
|
920
899
|
"""Create a single collection in the database.
|
|
921
900
|
|
|
922
901
|
Args:
|
|
923
902
|
collection (Collection): The Collection object to be created.
|
|
924
|
-
|
|
903
|
+
**kwargs: Additional keyword arguments.
|
|
904
|
+
- refresh (str): Whether to refresh the index after the operation. Can be "true", "false", or "wait_for".
|
|
905
|
+
- refresh (bool): Whether to refresh the index after the operation. Defaults to the value in `self.async_settings.database_refresh`.
|
|
925
906
|
|
|
926
907
|
Raises:
|
|
927
908
|
ConflictError: If a Collection with the same id already exists in the database.
|
|
928
909
|
|
|
910
|
+
Returns:
|
|
911
|
+
None
|
|
912
|
+
|
|
929
913
|
Notes:
|
|
930
914
|
A new index is created for the items in the Collection using the `create_item_index` function.
|
|
931
915
|
"""
|
|
932
916
|
collection_id = collection["id"]
|
|
933
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(f"Creating collection {collection_id} with refresh={refresh}")
|
|
927
|
+
|
|
928
|
+
# Check if the collection already exists
|
|
934
929
|
if await self.client.exists(index=COLLECTIONS_INDEX, id=collection_id):
|
|
935
930
|
raise ConflictError(f"Collection {collection_id} already exists")
|
|
936
931
|
|
|
932
|
+
# Index the collection in the database
|
|
937
933
|
await self.client.index(
|
|
938
934
|
index=COLLECTIONS_INDEX,
|
|
939
935
|
id=collection_id,
|
|
@@ -941,6 +937,7 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
941
937
|
refresh=refresh,
|
|
942
938
|
)
|
|
943
939
|
|
|
940
|
+
# Create the item index for the collection
|
|
944
941
|
await create_item_index(collection_id)
|
|
945
942
|
|
|
946
943
|
async def find_collection(self, collection_id: str) -> Collection:
|
|
@@ -970,29 +967,52 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
970
967
|
return collection["_source"]
|
|
971
968
|
|
|
972
969
|
async def update_collection(
|
|
973
|
-
self, collection_id: str, collection: Collection,
|
|
970
|
+
self, collection_id: str, collection: Collection, **kwargs: Any
|
|
974
971
|
):
|
|
975
|
-
"""Update a collection
|
|
972
|
+
"""Update a collection in the database.
|
|
976
973
|
|
|
977
974
|
Args:
|
|
978
|
-
self: The instance of the object calling this function.
|
|
979
975
|
collection_id (str): The ID of the collection to be updated.
|
|
980
976
|
collection (Collection): The Collection object to be used for the update.
|
|
977
|
+
**kwargs: Additional keyword arguments.
|
|
978
|
+
- refresh (str): Whether to refresh the index after the operation. Can be "true", "false", or "wait_for".
|
|
979
|
+
- refresh (bool): Whether to refresh the index after the operation. Defaults to the value in `self.async_settings.database_refresh`.
|
|
980
|
+
Returns:
|
|
981
|
+
None
|
|
981
982
|
|
|
982
983
|
Raises:
|
|
983
|
-
NotFoundError: If the collection with the given `collection_id` is not
|
|
984
|
-
|
|
984
|
+
NotFoundError: If the collection with the given `collection_id` is not found in the database.
|
|
985
|
+
ConflictError: If a conflict occurs during the update.
|
|
985
986
|
|
|
986
987
|
Notes:
|
|
987
988
|
This function updates the collection in the database using the specified
|
|
988
|
-
`collection_id` and
|
|
989
|
-
|
|
989
|
+
`collection_id` and the provided `Collection` object. If the collection ID
|
|
990
|
+
changes, the function creates a new collection, reindexes the items, and deletes
|
|
991
|
+
the old collection.
|
|
990
992
|
"""
|
|
993
|
+
# Ensure kwargs is a dictionary
|
|
994
|
+
kwargs = kwargs or {}
|
|
995
|
+
|
|
996
|
+
# Resolve the `refresh` parameter
|
|
997
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
998
|
+
refresh = validate_refresh(refresh)
|
|
999
|
+
|
|
1000
|
+
# Log the update attempt
|
|
1001
|
+
logger.info(f"Updating collection {collection_id} with refresh={refresh}")
|
|
1002
|
+
|
|
1003
|
+
# Ensure the collection exists
|
|
991
1004
|
await self.find_collection(collection_id=collection_id)
|
|
992
1005
|
|
|
1006
|
+
# Handle collection ID change
|
|
993
1007
|
if collection_id != collection["id"]:
|
|
1008
|
+
logger.info(
|
|
1009
|
+
f"Collection ID change detected: {collection_id} -> {collection['id']}"
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
# Create the new collection
|
|
994
1013
|
await self.create_collection(collection, refresh=refresh)
|
|
995
1014
|
|
|
1015
|
+
# Reindex items from the old collection to the new collection
|
|
996
1016
|
await self.client.reindex(
|
|
997
1017
|
body={
|
|
998
1018
|
"dest": {"index": f"{ITEMS_INDEX_PREFIX}{collection['id']}"},
|
|
@@ -1006,9 +1026,11 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1006
1026
|
refresh=refresh,
|
|
1007
1027
|
)
|
|
1008
1028
|
|
|
1029
|
+
# Delete the old collection
|
|
1009
1030
|
await self.delete_collection(collection_id)
|
|
1010
1031
|
|
|
1011
1032
|
else:
|
|
1033
|
+
# Update the existing collection
|
|
1012
1034
|
await self.client.index(
|
|
1013
1035
|
index=COLLECTIONS_INDEX,
|
|
1014
1036
|
id=collection_id,
|
|
@@ -1016,33 +1038,57 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1016
1038
|
refresh=refresh,
|
|
1017
1039
|
)
|
|
1018
1040
|
|
|
1019
|
-
async def delete_collection(self, collection_id: str,
|
|
1041
|
+
async def delete_collection(self, collection_id: str, **kwargs: Any):
|
|
1020
1042
|
"""Delete a collection from the database.
|
|
1021
1043
|
|
|
1022
1044
|
Parameters:
|
|
1023
|
-
self: The instance of the object calling this function.
|
|
1024
1045
|
collection_id (str): The ID of the collection to be deleted.
|
|
1025
|
-
|
|
1046
|
+
kwargs (Any, optional): Additional keyword arguments, including `refresh`.
|
|
1047
|
+
- refresh (str): Whether to refresh the index after the operation. Can be "true", "false", or "wait_for".
|
|
1048
|
+
- refresh (bool): Whether to refresh the index after the operation. Defaults to the value in `self.async_settings.database_refresh`.
|
|
1026
1049
|
|
|
1027
1050
|
Raises:
|
|
1028
1051
|
NotFoundError: If the collection with the given `collection_id` is not found in the database.
|
|
1029
1052
|
|
|
1053
|
+
Returns:
|
|
1054
|
+
None
|
|
1055
|
+
|
|
1030
1056
|
Notes:
|
|
1031
1057
|
This function first verifies that the collection with the specified `collection_id` exists in the database, and then
|
|
1032
|
-
deletes the collection. If `refresh` is set to
|
|
1033
|
-
function also calls `delete_item_index` to delete the index for the items in the collection.
|
|
1058
|
+
deletes the collection. If `refresh` is set to "true", "false", or "wait_for", the index is refreshed accordingly after
|
|
1059
|
+
the deletion. Additionally, this function also calls `delete_item_index` to delete the index for the items in the collection.
|
|
1034
1060
|
"""
|
|
1061
|
+
# Ensure kwargs is a dictionary
|
|
1062
|
+
kwargs = kwargs or {}
|
|
1063
|
+
|
|
1064
|
+
# Verify that the collection exists
|
|
1035
1065
|
await self.find_collection(collection_id=collection_id)
|
|
1066
|
+
|
|
1067
|
+
# Resolve the `refresh` parameter
|
|
1068
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
1069
|
+
refresh = validate_refresh(refresh)
|
|
1070
|
+
|
|
1071
|
+
# Log the deletion attempt
|
|
1072
|
+
logger.info(f"Deleting collection {collection_id} with refresh={refresh}")
|
|
1073
|
+
|
|
1074
|
+
# Delete the collection from the database
|
|
1036
1075
|
await self.client.delete(
|
|
1037
1076
|
index=COLLECTIONS_INDEX, id=collection_id, refresh=refresh
|
|
1038
1077
|
)
|
|
1039
|
-
|
|
1078
|
+
|
|
1079
|
+
# Delete the item index for the collection
|
|
1080
|
+
try:
|
|
1081
|
+
await delete_item_index(collection_id)
|
|
1082
|
+
except Exception as e:
|
|
1083
|
+
logger.error(
|
|
1084
|
+
f"Failed to delete item index for collection {collection_id}: {e}"
|
|
1085
|
+
)
|
|
1040
1086
|
|
|
1041
1087
|
async def bulk_async(
|
|
1042
1088
|
self,
|
|
1043
1089
|
collection_id: str,
|
|
1044
1090
|
processed_items: List[Item],
|
|
1045
|
-
|
|
1091
|
+
**kwargs: Any,
|
|
1046
1092
|
) -> Tuple[int, List[Dict[str, Any]]]:
|
|
1047
1093
|
"""
|
|
1048
1094
|
Perform a bulk insert of items into the database asynchronously.
|
|
@@ -1050,7 +1096,12 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1050
1096
|
Args:
|
|
1051
1097
|
collection_id (str): The ID of the collection to which the items belong.
|
|
1052
1098
|
processed_items (List[Item]): A list of `Item` objects to be inserted into the database.
|
|
1053
|
-
|
|
1099
|
+
**kwargs (Any): Additional keyword arguments, including:
|
|
1100
|
+
- refresh (str, optional): Whether to refresh the index after the bulk insert.
|
|
1101
|
+
Can be "true", "false", or "wait_for". Defaults to the value of `self.sync_settings.database_refresh`.
|
|
1102
|
+
- refresh (bool, optional): Whether to refresh the index after the bulk insert.
|
|
1103
|
+
- raise_on_error (bool, optional): Whether to raise an error if any of the bulk operations fail.
|
|
1104
|
+
Defaults to the value of `self.async_settings.raise_on_bulk_error`.
|
|
1054
1105
|
|
|
1055
1106
|
Returns:
|
|
1056
1107
|
Tuple[int, List[Dict[str, Any]]]: A tuple containing:
|
|
@@ -1059,10 +1110,31 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1059
1110
|
|
|
1060
1111
|
Notes:
|
|
1061
1112
|
This function performs a bulk insert of `processed_items` into the database using the specified `collection_id`.
|
|
1062
|
-
The insert is performed
|
|
1063
|
-
The `mk_actions` function is called to generate a list of actions for the bulk insert.
|
|
1064
|
-
the index is refreshed after the bulk insert
|
|
1113
|
+
The insert is performed synchronously and blocking, meaning that the function does not return until the insert has
|
|
1114
|
+
completed. The `mk_actions` function is called to generate a list of actions for the bulk insert. The `refresh`
|
|
1115
|
+
parameter determines whether the index is refreshed after the bulk insert:
|
|
1116
|
+
- "true": Forces an immediate refresh of the index.
|
|
1117
|
+
- "false": Does not refresh the index immediately (default behavior).
|
|
1118
|
+
- "wait_for": Waits for the next refresh cycle to make the changes visible.
|
|
1065
1119
|
"""
|
|
1120
|
+
# Ensure kwargs is a dictionary
|
|
1121
|
+
kwargs = kwargs or {}
|
|
1122
|
+
|
|
1123
|
+
# Resolve the `refresh` parameter
|
|
1124
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
1125
|
+
refresh = validate_refresh(refresh)
|
|
1126
|
+
|
|
1127
|
+
# Log the bulk insert attempt
|
|
1128
|
+
logger.info(
|
|
1129
|
+
f"Performing bulk insert for collection {collection_id} with refresh={refresh}"
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1132
|
+
# Handle empty processed_items
|
|
1133
|
+
if not processed_items:
|
|
1134
|
+
logger.warning(f"No items to insert for collection {collection_id}")
|
|
1135
|
+
return 0, []
|
|
1136
|
+
|
|
1137
|
+
# Perform the bulk insert
|
|
1066
1138
|
raise_on_error = self.async_settings.raise_on_bulk_error
|
|
1067
1139
|
success, errors = await helpers.async_bulk(
|
|
1068
1140
|
self.client,
|
|
@@ -1070,13 +1142,19 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1070
1142
|
refresh=refresh,
|
|
1071
1143
|
raise_on_error=raise_on_error,
|
|
1072
1144
|
)
|
|
1145
|
+
|
|
1146
|
+
# Log the result
|
|
1147
|
+
logger.info(
|
|
1148
|
+
f"Bulk insert completed for collection {collection_id}: {success} successes, {len(errors)} errors"
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1073
1151
|
return success, errors
|
|
1074
1152
|
|
|
1075
1153
|
def bulk_sync(
|
|
1076
1154
|
self,
|
|
1077
1155
|
collection_id: str,
|
|
1078
1156
|
processed_items: List[Item],
|
|
1079
|
-
|
|
1157
|
+
**kwargs: Any,
|
|
1080
1158
|
) -> Tuple[int, List[Dict[str, Any]]]:
|
|
1081
1159
|
"""
|
|
1082
1160
|
Perform a bulk insert of items into the database synchronously.
|
|
@@ -1084,7 +1162,12 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1084
1162
|
Args:
|
|
1085
1163
|
collection_id (str): The ID of the collection to which the items belong.
|
|
1086
1164
|
processed_items (List[Item]): A list of `Item` objects to be inserted into the database.
|
|
1087
|
-
|
|
1165
|
+
**kwargs (Any): Additional keyword arguments, including:
|
|
1166
|
+
- refresh (str, optional): Whether to refresh the index after the bulk insert.
|
|
1167
|
+
Can be "true", "false", or "wait_for". Defaults to the value of `self.sync_settings.database_refresh`.
|
|
1168
|
+
- refresh (bool, optional): Whether to refresh the index after the bulk insert.
|
|
1169
|
+
- raise_on_error (bool, optional): Whether to raise an error if any of the bulk operations fail.
|
|
1170
|
+
Defaults to the value of `self.async_settings.raise_on_bulk_error`.
|
|
1088
1171
|
|
|
1089
1172
|
Returns:
|
|
1090
1173
|
Tuple[int, List[Dict[str, Any]]]: A tuple containing:
|
|
@@ -1094,9 +1177,30 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1094
1177
|
Notes:
|
|
1095
1178
|
This function performs a bulk insert of `processed_items` into the database using the specified `collection_id`.
|
|
1096
1179
|
The insert is performed synchronously and blocking, meaning that the function does not return until the insert has
|
|
1097
|
-
completed. The `mk_actions` function is called to generate a list of actions for the bulk insert.
|
|
1098
|
-
|
|
1180
|
+
completed. The `mk_actions` function is called to generate a list of actions for the bulk insert. The `refresh`
|
|
1181
|
+
parameter determines whether the index is refreshed after the bulk insert:
|
|
1182
|
+
- "true": Forces an immediate refresh of the index.
|
|
1183
|
+
- "false": Does not refresh the index immediately (default behavior).
|
|
1184
|
+
- "wait_for": Waits for the next refresh cycle to make the changes visible.
|
|
1099
1185
|
"""
|
|
1186
|
+
# Ensure kwargs is a dictionary
|
|
1187
|
+
kwargs = kwargs or {}
|
|
1188
|
+
|
|
1189
|
+
# Resolve the `refresh` parameter
|
|
1190
|
+
refresh = kwargs.get("refresh", self.async_settings.database_refresh)
|
|
1191
|
+
refresh = validate_refresh(refresh)
|
|
1192
|
+
|
|
1193
|
+
# Log the bulk insert attempt
|
|
1194
|
+
logger.info(
|
|
1195
|
+
f"Performing bulk insert for collection {collection_id} with refresh={refresh}"
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
# Handle empty processed_items
|
|
1199
|
+
if not processed_items:
|
|
1200
|
+
logger.warning(f"No items to insert for collection {collection_id}")
|
|
1201
|
+
return 0, []
|
|
1202
|
+
|
|
1203
|
+
# Perform the bulk insert
|
|
1100
1204
|
raise_on_error = self.sync_settings.raise_on_bulk_error
|
|
1101
1205
|
success, errors = helpers.bulk(
|
|
1102
1206
|
self.sync_client,
|
|
@@ -1104,6 +1208,12 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
1104
1208
|
refresh=refresh,
|
|
1105
1209
|
raise_on_error=raise_on_error,
|
|
1106
1210
|
)
|
|
1211
|
+
|
|
1212
|
+
# Log the result
|
|
1213
|
+
logger.info(
|
|
1214
|
+
f"Bulk insert completed for collection {collection_id}: {success} successes, {len(errors)} errors"
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1107
1217
|
return success, errors
|
|
1108
1218
|
|
|
1109
1219
|
# DANGER
|