sfeos-helpers 6.8.0__py3-none-any.whl → 6.9.0__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.
- {sfeos_helpers-6.8.0.dist-info → sfeos_helpers-6.9.0.dist-info}/METADATA +2 -2
- {sfeos_helpers-6.8.0.dist-info → sfeos_helpers-6.9.0.dist-info}/RECORD +9 -9
- stac_fastapi/sfeos_helpers/aggregation/client.py +2 -2
- stac_fastapi/sfeos_helpers/database/index.py +6 -1
- stac_fastapi/sfeos_helpers/mappings.py +130 -2
- stac_fastapi/sfeos_helpers/models/patch.py +2 -5
- stac_fastapi/sfeos_helpers/search_engine/index_operations.py +11 -5
- stac_fastapi/sfeos_helpers/version.py +1 -1
- {sfeos_helpers-6.8.0.dist-info → sfeos_helpers-6.9.0.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sfeos_helpers
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.9.0
|
|
4
4
|
Summary: Helper library for the Elasticsearch and Opensearch stac-fastapi backends.
|
|
5
5
|
Project-URL: Homepage, https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
|
|
6
6
|
License: MIT
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Requires-Python: >=3.11
|
|
17
|
-
Requires-Dist: stac-fastapi-core==6.
|
|
17
|
+
Requires-Dist: stac-fastapi-core==6.9.0
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
|
|
20
20
|
# sfeos-helpers
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
stac_fastapi/sfeos_helpers/mappings.py,sha256=
|
|
2
|
-
stac_fastapi/sfeos_helpers/version.py,sha256=
|
|
1
|
+
stac_fastapi/sfeos_helpers/mappings.py,sha256=NTa2xMYLtF43qWEmCHuAVgC2mihgv2UKdwg_iv9I5KE,12952
|
|
2
|
+
stac_fastapi/sfeos_helpers/version.py,sha256=Zec8murh7xqMR7l6fB74XJcq2QdyNqRgOdhLMwBYPAI,45
|
|
3
3
|
stac_fastapi/sfeos_helpers/aggregation/README.md,sha256=SDlvCOpKyaJrlJvx84T2RzCnGALe_PK51zNeo3RP9ac,2122
|
|
4
4
|
stac_fastapi/sfeos_helpers/aggregation/__init__.py,sha256=Mym17lFh90by1GnoQgMyIKAqRNJnvCgVSXDYzjBiPQk,1210
|
|
5
|
-
stac_fastapi/sfeos_helpers/aggregation/client.py,sha256=
|
|
5
|
+
stac_fastapi/sfeos_helpers/aggregation/client.py,sha256=5aMZPMBbkS17QGLnuDCp9MyAeMj-Z3AOkzZ_JCRNohk,17921
|
|
6
6
|
stac_fastapi/sfeos_helpers/aggregation/format.py,sha256=qUW1jjh2EEjy-V7riliFR77grpi-AgsTmP76z60K5Lo,2011
|
|
7
7
|
stac_fastapi/sfeos_helpers/database/README.md,sha256=TVYFDD4PqDD57ZsWBv4i4LawaL_DAEIOjM6OQuqwLAU,4049
|
|
8
8
|
stac_fastapi/sfeos_helpers/database/__init__.py,sha256=Kvnz8hpXq_sSz8K5OW3PoPsvh9864Vv1zWhI5hxgd4o,2891
|
|
9
9
|
stac_fastapi/sfeos_helpers/database/datetime.py,sha256=LMh8dFjifpjfB_IKvOqQ7bQWMy4SXAYvWaLHMsad4tg,6806
|
|
10
10
|
stac_fastapi/sfeos_helpers/database/document.py,sha256=LtjX15gvaOuZC_k2t_oQhys_c-zRTLN5rwX0hNJkHnM,1725
|
|
11
|
-
stac_fastapi/sfeos_helpers/database/index.py,sha256=
|
|
11
|
+
stac_fastapi/sfeos_helpers/database/index.py,sha256=fddGwVJ3D5zuVj2DyTCujGk_hCXCV4ATCZRxAblo29s,6750
|
|
12
12
|
stac_fastapi/sfeos_helpers/database/mapping.py,sha256=4vGUuBLGoBOkQ984pYOciiz9UUWb4sbZyt-iViIsmdM,3809
|
|
13
13
|
stac_fastapi/sfeos_helpers/database/query.py,sha256=bbSYe0cLC7oFbhkHR5WTKCF7Ca9iZI3fdanD90KYN98,9476
|
|
14
14
|
stac_fastapi/sfeos_helpers/database/utils.py,sha256=CLtZgoUT37oklc9MsExXsxDviv4bzK-ZP7oxAOXS32Y,11780
|
|
@@ -17,11 +17,11 @@ stac_fastapi/sfeos_helpers/filter/__init__.py,sha256=n3zL_MhEGOoxMz1KeijyK_UKiZ0
|
|
|
17
17
|
stac_fastapi/sfeos_helpers/filter/client.py,sha256=_LX3mlW9MYhoMGxvyi7Eg-LJICElQXYGbzzhSPXYRtw,6517
|
|
18
18
|
stac_fastapi/sfeos_helpers/filter/cql2.py,sha256=Cg9kRYD9CVkVSyRqOyB5oVXmlyteSn2bw88sqklGpUM,955
|
|
19
19
|
stac_fastapi/sfeos_helpers/filter/transform.py,sha256=wu6t7jbhgK9JIROQ5W82sAyCn6lHnBwuoQzb6o63luI,5725
|
|
20
|
-
stac_fastapi/sfeos_helpers/models/patch.py,sha256=
|
|
20
|
+
stac_fastapi/sfeos_helpers/models/patch.py,sha256=krgopR6UvRV7NthfO61OwjDw9q2OHNLlk6WWaUyBrwk,3113
|
|
21
21
|
stac_fastapi/sfeos_helpers/search_engine/__init__.py,sha256=Bi0cAtul3FuLjFceTPtEcaWNBfmUX5vKaqDvbSUAm0o,754
|
|
22
22
|
stac_fastapi/sfeos_helpers/search_engine/base.py,sha256=9KOLW3NjW9PzWQzqLuhIjQU7FOHdDnB3ZNwDq469JZU,1400
|
|
23
23
|
stac_fastapi/sfeos_helpers/search_engine/factory.py,sha256=nPty3L8esypSVIzl5IKfmqQ1hVUIjMQ183Ksistr1bM,1066
|
|
24
|
-
stac_fastapi/sfeos_helpers/search_engine/index_operations.py,sha256=
|
|
24
|
+
stac_fastapi/sfeos_helpers/search_engine/index_operations.py,sha256=E4ipJVhAW_asah9QN-Coc2T0ir9rsPK6scDDt4jcdUA,5820
|
|
25
25
|
stac_fastapi/sfeos_helpers/search_engine/inserters.py,sha256=o-I_4OowMJetMwRFPdq8Oix_DAkMNGBw4fYyoa5W6s0,10562
|
|
26
26
|
stac_fastapi/sfeos_helpers/search_engine/managers.py,sha256=nldomKmw8iQfOxeGZbBRGG_rWk-vB5Hy_cOjJ2e0ArE,6454
|
|
27
27
|
stac_fastapi/sfeos_helpers/search_engine/selection/__init__.py,sha256=qKd4KzZkERwF_yhIeFcjAUnq5vQarr3CuXxE3SWmt6c,441
|
|
@@ -29,6 +29,6 @@ stac_fastapi/sfeos_helpers/search_engine/selection/base.py,sha256=106c4FK50cgMmT
|
|
|
29
29
|
stac_fastapi/sfeos_helpers/search_engine/selection/cache_manager.py,sha256=jG5XYWocCfhMgopA0bknGdw6R6zZ1cjanlX2554TFTA,4039
|
|
30
30
|
stac_fastapi/sfeos_helpers/search_engine/selection/factory.py,sha256=vbgNVCUW2lviePqzpgsPLxp6IEqcX3GHiahqN2oVObA,1305
|
|
31
31
|
stac_fastapi/sfeos_helpers/search_engine/selection/selectors.py,sha256=q83nfCfNfLUqtkHpORwNHNRU9Pa-heeaDIPO0RlHb-8,4779
|
|
32
|
-
sfeos_helpers-6.
|
|
33
|
-
sfeos_helpers-6.
|
|
34
|
-
sfeos_helpers-6.
|
|
32
|
+
sfeos_helpers-6.9.0.dist-info/METADATA,sha256=yuWwHyz0TyeDHyyetqj7eqhQ-FoflATvVQqIPsXzalI,3114
|
|
33
|
+
sfeos_helpers-6.9.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
34
|
+
sfeos_helpers-6.9.0.dist-info/RECORD,,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Client implementation for the STAC API Aggregation Extension."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
from typing import Annotated, Any, Dict, List, Optional, Union
|
|
5
5
|
from urllib.parse import unquote_plus, urljoin
|
|
6
6
|
|
|
7
7
|
import attr
|
|
8
8
|
import orjson
|
|
9
|
-
from fastapi import HTTPException, Request
|
|
9
|
+
from fastapi import HTTPException, Path, Request
|
|
10
10
|
from pygeofilter.backends.cql2_json import to_cql2
|
|
11
11
|
from pygeofilter.parsers.cql2_text import parse as parse_cql2_text
|
|
12
12
|
from stac_pydantic.shared import BBox
|
|
@@ -177,7 +177,12 @@ async def delete_item_index_shared(settings: Any, collection_id: str) -> None:
|
|
|
177
177
|
client = settings.create_client
|
|
178
178
|
|
|
179
179
|
name = index_alias_by_collection_id(collection_id)
|
|
180
|
-
|
|
180
|
+
if hasattr(client, "options"):
|
|
181
|
+
resolved = await client.options(ignore_status=[404]).indices.resolve_index(
|
|
182
|
+
name=name
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
resolved = await client.indices.resolve_index(name=name, ignore=[404])
|
|
181
186
|
if "aliases" in resolved and resolved["aliases"]:
|
|
182
187
|
[alias] = resolved["aliases"]
|
|
183
188
|
await client.indices.delete_alias(index=alias["indices"], name=alias["name"])
|
|
@@ -25,11 +25,135 @@ Function Naming Conventions:
|
|
|
25
25
|
- Parameter names should be consistent across similar functions
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
+
import copy
|
|
29
|
+
import json
|
|
30
|
+
import logging
|
|
28
31
|
import os
|
|
29
|
-
from typing import Any, Dict, Literal, Protocol
|
|
32
|
+
from typing import Any, Dict, Literal, Optional, Protocol, Union
|
|
30
33
|
|
|
31
34
|
from stac_fastapi.core.utilities import get_bool_env
|
|
32
35
|
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def merge_mappings(base: Dict[str, Any], custom: Dict[str, Any]) -> None:
|
|
40
|
+
"""Recursively merge custom mappings into base mappings.
|
|
41
|
+
|
|
42
|
+
Custom mappings will overwrite base mappings if keys collide.
|
|
43
|
+
Nested dictionaries are merged recursively.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
base: The base mapping dictionary to merge into (modified in place).
|
|
47
|
+
custom: The custom mapping dictionary to merge from.
|
|
48
|
+
"""
|
|
49
|
+
for key, value in custom.items():
|
|
50
|
+
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
|
51
|
+
merge_mappings(base[key], value)
|
|
52
|
+
else:
|
|
53
|
+
base[key] = value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def parse_dynamic_mapping_config(
|
|
57
|
+
config_value: Optional[str],
|
|
58
|
+
) -> Union[bool, str]:
|
|
59
|
+
"""Parse the dynamic mapping configuration value.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
config_value: The configuration value from environment variable.
|
|
63
|
+
Can be "true", "false", "strict", or None.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True for "true" (default), False for "false", or the string value
|
|
67
|
+
for other settings like "strict".
|
|
68
|
+
"""
|
|
69
|
+
if config_value is None:
|
|
70
|
+
return True
|
|
71
|
+
config_lower = config_value.lower()
|
|
72
|
+
if config_lower == "true":
|
|
73
|
+
return True
|
|
74
|
+
elif config_lower == "false":
|
|
75
|
+
return False
|
|
76
|
+
else:
|
|
77
|
+
return config_lower
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def apply_custom_mappings(
|
|
81
|
+
mappings: Dict[str, Any], custom_mappings_json: Optional[str]
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Apply custom mappings from a JSON string to the mappings dictionary.
|
|
84
|
+
|
|
85
|
+
The custom mappings JSON should have the same structure as ES_ITEMS_MAPPINGS.
|
|
86
|
+
It will be recursively merged at the root level, allowing users to override
|
|
87
|
+
any part of the mapping including properties, dynamic_templates, etc.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
mappings: The mappings dictionary to modify (modified in place).
|
|
91
|
+
custom_mappings_json: JSON string containing custom mappings.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
Logs error if JSON parsing or merging fails.
|
|
95
|
+
"""
|
|
96
|
+
if not custom_mappings_json:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
custom_mappings = json.loads(custom_mappings_json)
|
|
101
|
+
merge_mappings(mappings, custom_mappings)
|
|
102
|
+
except json.JSONDecodeError as e:
|
|
103
|
+
logger.error(f"Failed to parse STAC_FASTAPI_ES_CUSTOM_MAPPINGS JSON: {e}")
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Failed to merge STAC_FASTAPI_ES_CUSTOM_MAPPINGS: {e}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_items_mappings(
|
|
109
|
+
dynamic_mapping: Optional[str] = None, custom_mappings: Optional[str] = None
|
|
110
|
+
) -> Dict[str, Any]:
|
|
111
|
+
"""Get the ES_ITEMS_MAPPINGS with optional dynamic mapping and custom mappings applied.
|
|
112
|
+
|
|
113
|
+
This function creates a fresh copy of the base mappings and applies the
|
|
114
|
+
specified configuration. Useful for testing or programmatic configuration.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
dynamic_mapping: Override for STAC_FASTAPI_ES_DYNAMIC_MAPPING.
|
|
118
|
+
If None, reads from environment variable.
|
|
119
|
+
custom_mappings: Override for STAC_FASTAPI_ES_CUSTOM_MAPPINGS.
|
|
120
|
+
If None, reads from environment variable.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
A new dictionary containing the configured mappings.
|
|
124
|
+
"""
|
|
125
|
+
mappings = copy.deepcopy(_BASE_ITEMS_MAPPINGS)
|
|
126
|
+
|
|
127
|
+
# Apply dynamic mapping configuration
|
|
128
|
+
dynamic_config = (
|
|
129
|
+
dynamic_mapping
|
|
130
|
+
if dynamic_mapping is not None
|
|
131
|
+
else os.getenv("STAC_FASTAPI_ES_DYNAMIC_MAPPING", "true")
|
|
132
|
+
)
|
|
133
|
+
mappings["dynamic"] = parse_dynamic_mapping_config(dynamic_config)
|
|
134
|
+
|
|
135
|
+
# Apply custom mappings
|
|
136
|
+
custom_config = (
|
|
137
|
+
custom_mappings
|
|
138
|
+
if custom_mappings is not None
|
|
139
|
+
else os.getenv("STAC_FASTAPI_ES_CUSTOM_MAPPINGS")
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if custom_config is None:
|
|
143
|
+
mappings_file = os.getenv("STAC_FASTAPI_ES_MAPPINGS_FILE")
|
|
144
|
+
if mappings_file:
|
|
145
|
+
try:
|
|
146
|
+
with open(mappings_file, "r") as f:
|
|
147
|
+
custom_config = f.read()
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(
|
|
150
|
+
f"Failed to read STAC_FASTAPI_ES_MAPPINGS_FILE at {mappings_file}: {e}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
apply_custom_mappings(mappings, custom_config)
|
|
154
|
+
|
|
155
|
+
return mappings
|
|
156
|
+
|
|
33
157
|
|
|
34
158
|
# stac_pydantic classes extend _GeometryBase, which doesn't have a type field,
|
|
35
159
|
# So create our own Protocol for typing
|
|
@@ -129,7 +253,8 @@ ES_MAPPINGS_DYNAMIC_TEMPLATES = [
|
|
|
129
253
|
},
|
|
130
254
|
]
|
|
131
255
|
|
|
132
|
-
|
|
256
|
+
# Base items mappings without dynamic configuration applied
|
|
257
|
+
_BASE_ITEMS_MAPPINGS = {
|
|
133
258
|
"numeric_detection": False,
|
|
134
259
|
"dynamic_templates": ES_MAPPINGS_DYNAMIC_TEMPLATES,
|
|
135
260
|
"properties": {
|
|
@@ -155,6 +280,9 @@ ES_ITEMS_MAPPINGS = {
|
|
|
155
280
|
},
|
|
156
281
|
}
|
|
157
282
|
|
|
283
|
+
# ES_ITEMS_MAPPINGS with environment-based configuration applied at module load time
|
|
284
|
+
ES_ITEMS_MAPPINGS = get_items_mappings()
|
|
285
|
+
|
|
158
286
|
ES_COLLECTIONS_MAPPINGS = {
|
|
159
287
|
"numeric_detection": False,
|
|
160
288
|
"dynamic_templates": ES_MAPPINGS_DYNAMIC_TEMPLATES,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import re
|
|
4
4
|
from typing import Any, Dict, Optional, Union
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, model_validator
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
7
7
|
|
|
8
8
|
regex = re.compile(r"([^.' ]*:[^.'[ ]*)\.?")
|
|
9
9
|
replacements = str.maketrans({"/": "", ".": "", ":": "", "[": "", "]": ""})
|
|
@@ -84,10 +84,7 @@ class ElasticPath(BaseModel):
|
|
|
84
84
|
variable_name: Optional[str] = None
|
|
85
85
|
param_key: Optional[str] = None
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
"""Class config."""
|
|
89
|
-
|
|
90
|
-
frozen = True
|
|
87
|
+
model_config = ConfigDict(frozen=True)
|
|
91
88
|
|
|
92
89
|
@model_validator(mode="before")
|
|
93
90
|
@classmethod
|
|
@@ -31,11 +31,17 @@ class IndexOperations:
|
|
|
31
31
|
index_name = f"{index_by_collection_id(collection_id)}-000001"
|
|
32
32
|
alias_name = index_alias_by_collection_id(collection_id)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
if hasattr(client, "options"):
|
|
35
|
+
await client.options(ignore_status=[400]).indices.create(
|
|
36
|
+
index=index_name,
|
|
37
|
+
body=self._create_index_body({alias_name: {}}),
|
|
38
|
+
)
|
|
39
|
+
else:
|
|
40
|
+
await client.indices.create(
|
|
41
|
+
index=index_name,
|
|
42
|
+
body=self._create_index_body({alias_name: {}}),
|
|
43
|
+
params={"ignore": [400]},
|
|
44
|
+
)
|
|
39
45
|
return index_name
|
|
40
46
|
|
|
41
47
|
async def create_datetime_index(
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.9.0"
|
|
File without changes
|