stac-fastapi-elasticsearch 4.2.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 +4 -4
- stac_fastapi/elasticsearch/config.py +2 -1
- stac_fastapi/elasticsearch/database_logic.py +69 -156
- 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.2.0.dist-info/METADATA +0 -382
- stac_fastapi_elasticsearch-4.2.0.dist-info/RECORD +0 -10
- {stac_fastapi_elasticsearch-4.2.0.dist-info → stac_fastapi_elasticsearch-5.0.0a0.dist-info}/WHEEL +0 -0
- {stac_fastapi_elasticsearch-4.2.0.dist-info → stac_fastapi_elasticsearch-5.0.0a0.dist-info}/entry_points.txt +0 -0
- {stac_fastapi_elasticsearch-4.2.0.dist-info → stac_fastapi_elasticsearch-5.0.0a0.dist-info}/top_level.txt +0 -0
|
@@ -11,14 +11,12 @@ from stac_fastapi.api.models import create_get_request_model, create_post_reques
|
|
|
11
11
|
from stac_fastapi.core.core import (
|
|
12
12
|
BulkTransactionsClient,
|
|
13
13
|
CoreClient,
|
|
14
|
-
EsAsyncBaseFiltersClient,
|
|
15
14
|
TransactionsClient,
|
|
16
15
|
)
|
|
17
16
|
from stac_fastapi.core.extensions import QueryExtension
|
|
18
17
|
from stac_fastapi.core.extensions.aggregation import (
|
|
19
18
|
EsAggregationExtensionGetRequest,
|
|
20
19
|
EsAggregationExtensionPostRequest,
|
|
21
|
-
EsAsyncAggregationClient,
|
|
22
20
|
)
|
|
23
21
|
from stac_fastapi.core.extensions.fields import FieldsExtension
|
|
24
22
|
from stac_fastapi.core.rate_limit import setup_rate_limit
|
|
@@ -40,6 +38,8 @@ from stac_fastapi.extensions.core import (
|
|
|
40
38
|
TransactionExtension,
|
|
41
39
|
)
|
|
42
40
|
from stac_fastapi.extensions.third_party import BulkTransactionExtension
|
|
41
|
+
from stac_fastapi.sfeos_helpers.aggregation import EsAsyncBaseAggregationClient
|
|
42
|
+
from stac_fastapi.sfeos_helpers.filter import EsAsyncBaseFiltersClient
|
|
43
43
|
|
|
44
44
|
logging.basicConfig(level=logging.INFO)
|
|
45
45
|
logger = logging.getLogger(__name__)
|
|
@@ -60,7 +60,7 @@ filter_extension.conformance_classes.append(
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
aggregation_extension = AggregationExtension(
|
|
63
|
-
client=
|
|
63
|
+
client=EsAsyncBaseAggregationClient(
|
|
64
64
|
database=database_logic, session=session, settings=settings
|
|
65
65
|
)
|
|
66
66
|
)
|
|
@@ -106,7 +106,7 @@ post_request_model = create_post_request_model(search_extensions)
|
|
|
106
106
|
api = StacApi(
|
|
107
107
|
title=os.getenv("STAC_FASTAPI_TITLE", "stac-fastapi-elasticsearch"),
|
|
108
108
|
description=os.getenv("STAC_FASTAPI_DESCRIPTION", "stac-fastapi-elasticsearch"),
|
|
109
|
-
api_version=os.getenv("STAC_FASTAPI_VERSION", "
|
|
109
|
+
api_version=os.getenv("STAC_FASTAPI_VERSION", "5.0.0a0"),
|
|
110
110
|
settings=settings,
|
|
111
111
|
extensions=extensions,
|
|
112
112
|
client=CoreClient(
|
|
@@ -10,7 +10,8 @@ from elasticsearch._async.client import AsyncElasticsearch
|
|
|
10
10
|
|
|
11
11
|
from elasticsearch import Elasticsearch # type: ignore[attr-defined]
|
|
12
12
|
from stac_fastapi.core.base_settings import ApiBaseSettings
|
|
13
|
-
from stac_fastapi.core.utilities import get_bool_env
|
|
13
|
+
from stac_fastapi.core.utilities import get_bool_env
|
|
14
|
+
from stac_fastapi.sfeos_helpers.database import validate_refresh
|
|
14
15
|
from stac_fastapi.types.config import ApiSettings
|
|
15
16
|
|
|
16
17
|
|
|
@@ -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
|
|
|
@@ -300,23 +220,12 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
300
220
|
Returns:
|
|
301
221
|
dict: A dictionary containing the Queryables mappings.
|
|
302
222
|
"""
|
|
303
|
-
queryables_mapping = {}
|
|
304
|
-
|
|
305
223
|
mappings = await self.client.indices.get_mapping(
|
|
306
224
|
index=f"{ITEMS_INDEX_PREFIX}{collection_id}",
|
|
307
225
|
)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
properties = fields.pop("properties", {}).get("properties", {}).keys()
|
|
312
|
-
|
|
313
|
-
for field_key in fields:
|
|
314
|
-
queryables_mapping[field_key] = field_key
|
|
315
|
-
|
|
316
|
-
for property_key in properties:
|
|
317
|
-
queryables_mapping[property_key] = f"properties.{property_key}"
|
|
318
|
-
|
|
319
|
-
return queryables_mapping
|
|
226
|
+
return await get_queryables_mapping_shared(
|
|
227
|
+
collection_id=collection_id, mappings=mappings
|
|
228
|
+
)
|
|
320
229
|
|
|
321
230
|
@staticmethod
|
|
322
231
|
def make_search():
|
|
@@ -334,17 +243,20 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
334
243
|
return search.filter("terms", collection=collection_ids)
|
|
335
244
|
|
|
336
245
|
@staticmethod
|
|
337
|
-
def apply_datetime_filter(
|
|
246
|
+
def apply_datetime_filter(
|
|
247
|
+
search: Search, interval: Optional[Union[DateTimeType, str]]
|
|
248
|
+
):
|
|
338
249
|
"""Apply a filter to search on datetime, start_datetime, and end_datetime fields.
|
|
339
250
|
|
|
340
251
|
Args:
|
|
341
252
|
search (Search): The search object to filter.
|
|
342
|
-
|
|
253
|
+
interval: Optional[Union[DateTimeType, str]]
|
|
343
254
|
|
|
344
255
|
Returns:
|
|
345
256
|
Search: The filtered search object.
|
|
346
257
|
"""
|
|
347
258
|
should = []
|
|
259
|
+
datetime_search = return_date(interval)
|
|
348
260
|
|
|
349
261
|
# If the request is a single datetime return
|
|
350
262
|
# items with datetimes equal to the requested datetime OR
|
|
@@ -497,21 +409,8 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
497
409
|
Notes:
|
|
498
410
|
A geo_shape filter is added to the search object, set to intersect with the specified geometry.
|
|
499
411
|
"""
|
|
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
|
-
)
|
|
412
|
+
filter = apply_intersects_filter_shared(intersects=intersects)
|
|
413
|
+
return search.filter(Q(filter))
|
|
515
414
|
|
|
516
415
|
@staticmethod
|
|
517
416
|
def apply_stacql_filter(search: Search, op: str, field: str, value: float):
|
|
@@ -537,14 +436,21 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
537
436
|
|
|
538
437
|
@staticmethod
|
|
539
438
|
def apply_free_text_filter(search: Search, free_text_queries: Optional[List[str]]):
|
|
540
|
-
"""
|
|
541
|
-
if free_text_queries is not None:
|
|
542
|
-
free_text_query_string = '" OR properties.\\*:"'.join(free_text_queries)
|
|
543
|
-
search = search.query(
|
|
544
|
-
"query_string", query=f'properties.\\*:"{free_text_query_string}"'
|
|
545
|
-
)
|
|
439
|
+
"""Create a free text query for Elasticsearch queries.
|
|
546
440
|
|
|
547
|
-
|
|
441
|
+
This method delegates to the shared implementation in apply_free_text_filter_shared.
|
|
442
|
+
|
|
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
|
+
)
|
|
548
454
|
|
|
549
455
|
async def apply_cql2_filter(
|
|
550
456
|
self, search: Search, _filter: Optional[Dict[str, Any]]
|
|
@@ -575,11 +481,18 @@ class DatabaseLogic(BaseDatabaseLogic):
|
|
|
575
481
|
|
|
576
482
|
@staticmethod
|
|
577
483
|
def populate_sort(sortby: List) -> Optional[Dict[str, Dict[str, str]]]:
|
|
578
|
-
"""
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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)
|
|
583
496
|
|
|
584
497
|
async def execute_search(
|
|
585
498
|
self,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "
|
|
2
|
+
__version__ = "5.0.0a0"
|