sfeos-helpers 6.8.1__tar.gz → 6.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/PKG-INFO +2 -2
  2. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/pyproject.toml +1 -1
  3. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/aggregation/client.py +2 -2
  4. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/database/index.py +6 -1
  5. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/mappings.py +130 -2
  6. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/models/patch.py +2 -5
  7. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/index_operations.py +11 -5
  8. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/version.py +1 -1
  9. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/.gitignore +0 -0
  10. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/README.md +0 -0
  11. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/aggregation/README.md +0 -0
  12. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/aggregation/__init__.py +0 -0
  13. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/aggregation/format.py +0 -0
  14. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/database/README.md +0 -0
  15. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/database/__init__.py +0 -0
  16. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/database/datetime.py +0 -0
  17. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/database/document.py +0 -0
  18. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/database/mapping.py +0 -0
  19. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/database/query.py +0 -0
  20. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/database/utils.py +0 -0
  21. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/filter/README.md +0 -0
  22. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/filter/__init__.py +0 -0
  23. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/filter/client.py +0 -0
  24. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/filter/cql2.py +0 -0
  25. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/filter/transform.py +0 -0
  26. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/__init__.py +0 -0
  27. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/base.py +0 -0
  28. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/factory.py +0 -0
  29. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/inserters.py +0 -0
  30. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/managers.py +0 -0
  31. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/selection/__init__.py +0 -0
  32. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/selection/base.py +0 -0
  33. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/selection/cache_manager.py +0 -0
  34. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/selection/factory.py +0 -0
  35. {sfeos_helpers-6.8.1 → sfeos_helpers-6.9.0}/stac_fastapi/sfeos_helpers/search_engine/selection/selectors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sfeos_helpers
3
- Version: 6.8.1
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.8.1
17
+ Requires-Dist: stac-fastapi-core==6.9.0
18
18
  Description-Content-Type: text/markdown
19
19
 
20
20
  # sfeos-helpers
@@ -29,7 +29,7 @@ keywords = [
29
29
  ]
30
30
  dynamic = ["version"]
31
31
  dependencies = [
32
- "stac-fastapi.core==6.8.1",
32
+ "stac-fastapi.core==6.9.0",
33
33
  ]
34
34
 
35
35
  [project.urls]
@@ -1,12 +1,12 @@
1
1
  """Client implementation for the STAC API Aggregation Extension."""
2
2
 
3
- from pathlib import Path
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
- resolved = await client.indices.resolve_index(name=name, ignore=[404])
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
- ES_ITEMS_MAPPINGS = {
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
- class Config:
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
- await client.indices.create(
35
- index=index_name,
36
- body=self._create_index_body({alias_name: {}}),
37
- params={"ignore": [400]},
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.8.1"
2
+ __version__ = "6.9.0"
File without changes
File without changes