sfeos-helpers 6.7.6__tar.gz → 6.8.1__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 (36) hide show
  1. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/PKG-INFO +3 -3
  2. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/README.md +1 -1
  3. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/pyproject.toml +1 -1
  4. sfeos_helpers-6.8.1/stac_fastapi/sfeos_helpers/database/mapping.py +111 -0
  5. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/filter/client.py +44 -9
  6. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/filter/transform.py +14 -1
  7. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/mappings.py +1 -0
  8. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/selection/cache_manager.py +1 -1
  9. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/version.py +1 -1
  10. sfeos_helpers-6.7.6/stac_fastapi/sfeos_helpers/database/mapping.py +0 -38
  11. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/.gitignore +0 -0
  12. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/aggregation/README.md +0 -0
  13. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/aggregation/__init__.py +0 -0
  14. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/aggregation/client.py +0 -0
  15. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/aggregation/format.py +0 -0
  16. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/database/README.md +0 -0
  17. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/database/__init__.py +0 -0
  18. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/database/datetime.py +0 -0
  19. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/database/document.py +0 -0
  20. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/database/index.py +0 -0
  21. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/database/query.py +0 -0
  22. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/database/utils.py +0 -0
  23. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/filter/README.md +0 -0
  24. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/filter/__init__.py +0 -0
  25. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/filter/cql2.py +0 -0
  26. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/models/patch.py +0 -0
  27. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/__init__.py +0 -0
  28. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/base.py +0 -0
  29. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/factory.py +0 -0
  30. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/index_operations.py +0 -0
  31. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/inserters.py +0 -0
  32. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/managers.py +0 -0
  33. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/selection/__init__.py +0 -0
  34. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/selection/base.py +0 -0
  35. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/stac_fastapi/sfeos_helpers/search_engine/selection/factory.py +0 -0
  36. {sfeos_helpers-6.7.6 → sfeos_helpers-6.8.1}/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.7.6
3
+ Version: 6.8.1
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.7.6
17
+ Requires-Dist: stac-fastapi-core==6.8.1
18
18
  Description-Content-Type: text/markdown
19
19
 
20
20
  # sfeos-helpers
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
29
29
  [![GitHub forks](https://img.shields.io/github/forks/stac-utils/stac-fastapi-elasticsearch-opensearch.svg?color=blue)](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/network/members)
30
30
  [![PyPI version](https://img.shields.io/pypi/v/stac-fastapi-elasticsearch.svg?color=blue)](https://pypi.org/project/stac-fastapi-elasticsearch/)
31
31
  [![STAC](https://img.shields.io/badge/STAC-1.1.0-blue.svg)](https://github.com/radiantearth/stac-spec/tree/v1.1.0)
32
- [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.0.0-blue.svg)](https://github.com/stac-utils/stac-fastapi)
32
+ [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.1.1-blue.svg)](https://github.com/stac-utils/stac-fastapi)
33
33
 
34
34
  Helper utilities for the stac-fastapi project. For full documentation, please see the [main README](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/blob/main/README.md).
35
35
 
@@ -10,7 +10,7 @@
10
10
  [![GitHub forks](https://img.shields.io/github/forks/stac-utils/stac-fastapi-elasticsearch-opensearch.svg?color=blue)](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/network/members)
11
11
  [![PyPI version](https://img.shields.io/pypi/v/stac-fastapi-elasticsearch.svg?color=blue)](https://pypi.org/project/stac-fastapi-elasticsearch/)
12
12
  [![STAC](https://img.shields.io/badge/STAC-1.1.0-blue.svg)](https://github.com/radiantearth/stac-spec/tree/v1.1.0)
13
- [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.0.0-blue.svg)](https://github.com/stac-utils/stac-fastapi)
13
+ [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.1.1-blue.svg)](https://github.com/stac-utils/stac-fastapi)
14
14
 
15
15
  Helper utilities for the stac-fastapi project. For full documentation, please see the [main README](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/blob/main/README.md).
16
16
 
@@ -29,7 +29,7 @@ keywords = [
29
29
  ]
30
30
  dynamic = ["version"]
31
31
  dependencies = [
32
- "stac-fastapi.core==6.7.6",
32
+ "stac-fastapi.core==6.8.1",
33
33
  ]
34
34
 
35
35
  [project.urls]
@@ -0,0 +1,111 @@
1
+ """Mapping functions for Elasticsearch/OpenSearch.
2
+
3
+ This module provides functions for working with Elasticsearch/OpenSearch mappings.
4
+ """
5
+
6
+ import os
7
+ from collections import deque
8
+ from typing import Any, Dict, Set
9
+
10
+
11
+ def _get_excluded_from_queryables() -> Set[str]:
12
+ """Get fields to exclude from queryables endpoint and filtering.
13
+
14
+ Reads from EXCLUDED_FROM_QUERYABLES environment variable.
15
+ Supports comma-separated list of field names.
16
+
17
+ For each exclusion pattern, both the original and the version with/without
18
+ 'properties.' prefix are included. This ensures fields are excluded regardless
19
+ of whether they appear at the top level or under 'properties' in the mapping.
20
+
21
+ Example:
22
+ EXCLUDED_FROM_QUERYABLES="properties.auth:schemes,storage:schemes"
23
+
24
+ This will exclude:
25
+ - properties.auth:schemes (and children like properties.auth:schemes.s3.type)
26
+ - auth:schemes (and children like auth:schemes.s3.type)
27
+ - storage:schemes (and children)
28
+ - properties.storage:schemes (and children)
29
+
30
+ Returns:
31
+ Set[str]: Set of field names to exclude from queryables
32
+ """
33
+ excluded = os.getenv("EXCLUDED_FROM_QUERYABLES", "")
34
+ if not excluded:
35
+ return set()
36
+
37
+ result = set()
38
+ for field in excluded.split(","):
39
+ field = field.strip()
40
+ if not field:
41
+ continue
42
+
43
+ result.add(field)
44
+
45
+ if field.startswith("properties."):
46
+ result.add(field.removeprefix("properties."))
47
+ else:
48
+ result.add(f"properties.{field}")
49
+
50
+ return result
51
+
52
+
53
+ async def get_queryables_mapping_shared(
54
+ mappings: Dict[str, Dict[str, Any]],
55
+ collection_id: str = "*",
56
+ ) -> Dict[str, str]:
57
+ """Retrieve mapping of Queryables for search.
58
+
59
+ Fields listed in the EXCLUDED_FROM_QUERYABLES environment variable will be
60
+ excluded from the result, along with their children.
61
+
62
+ Args:
63
+ mappings (Dict[str, Dict[str, Any]]): The mapping information returned from
64
+ Elasticsearch/OpenSearch client's indices.get_mapping() method.
65
+ Expected structure is {index_name: {"mappings": {...}}}.
66
+ collection_id (str, optional): The id of the Collection the Queryables
67
+ belongs to. Defaults to "*".
68
+
69
+ Returns:
70
+ Dict[str, str]: A dictionary containing the Queryables mappings, where keys are
71
+ field names (with 'properties.' prefix removed) and values are the
72
+ corresponding paths in the Elasticsearch/OpenSearch document structure.
73
+ """
74
+ queryables_mapping = {}
75
+ excluded = _get_excluded_from_queryables()
76
+
77
+ def is_excluded(path: str) -> bool:
78
+ """Check if the path starts with any excluded prefix."""
79
+ return any(
80
+ path == prefix or path.startswith(prefix + ".") for prefix in excluded
81
+ )
82
+
83
+ for mapping in mappings.values():
84
+ mapping_properties = mapping["mappings"].get("properties", {})
85
+
86
+ stack: deque[tuple[str, Dict[str, Any]]] = deque(mapping_properties.items())
87
+
88
+ while stack:
89
+ field_fqn, field_def = stack.popleft()
90
+
91
+ nested_properties = field_def.get("properties")
92
+ if nested_properties:
93
+ stack.extend(
94
+ (f"{field_fqn}.{k}", v)
95
+ for k, v in nested_properties.items()
96
+ if v.get("enabled", True) and not is_excluded(f"{field_fqn}.{k}")
97
+ )
98
+
99
+ field_type = field_def.get("type")
100
+ if (
101
+ not field_type
102
+ or not field_def.get("enabled", True)
103
+ or is_excluded(field_fqn)
104
+ ):
105
+ continue
106
+
107
+ field_name = field_fqn.removeprefix("properties.")
108
+
109
+ queryables_mapping[field_name] = field_fqn
110
+
111
+ return queryables_mapping
@@ -26,8 +26,12 @@ class EsAsyncBaseFiltersClient(AsyncBaseFiltersClient):
26
26
  Reads from EXCLUDED_FROM_QUERYABLES environment variable.
27
27
  Supports comma-separated list of field names.
28
28
 
29
+ For each exclusion pattern, both the original and the version with/without
30
+ 'properties.' prefix are included. This ensures fields are excluded regardless
31
+ of whether they appear at the top level or under 'properties' in the mapping.
32
+
29
33
  Example:
30
- EXCLUDED_FROM_QUERYABLES="auth:schemes,storage:schemes"
34
+ EXCLUDED_FROM_QUERYABLES="properties.auth:schemes,storage:schemes"
31
35
 
32
36
  Returns:
33
37
  Set[str]: Set of field names to exclude from queryables
@@ -35,7 +39,41 @@ class EsAsyncBaseFiltersClient(AsyncBaseFiltersClient):
35
39
  excluded = os.getenv("EXCLUDED_FROM_QUERYABLES", "")
36
40
  if not excluded:
37
41
  return set()
38
- return {field.strip() for field in excluded.split(",") if field.strip()}
42
+
43
+ result = set()
44
+ for field in excluded.split(","):
45
+ field = field.strip()
46
+ if not field:
47
+ continue
48
+
49
+ result.add(field)
50
+
51
+ if field.startswith("properties."):
52
+ result.add(field.removeprefix("properties."))
53
+ else:
54
+ result.add(f"properties.{field}")
55
+
56
+ return result
57
+
58
+ @staticmethod
59
+ def _is_excluded(field_fqn: str, excluded: set[str]) -> bool:
60
+ """Check if a field should be excluded based on prefix matching.
61
+
62
+ A field is excluded if:
63
+ - It exactly matches an exclusion pattern
64
+ - It starts with an exclusion pattern followed by a dot (nested child)
65
+
66
+ Args:
67
+ field_fqn: Fully qualified field name (e.g., "properties.auth:schemes.s3.type")
68
+ excluded: Set of exclusion patterns
69
+
70
+ Returns:
71
+ True if field should be excluded, False otherwise
72
+ """
73
+ for prefix in excluded:
74
+ if field_fqn == prefix or field_fqn.startswith(prefix + "."):
75
+ return True
76
+ return False
39
77
 
40
78
  async def get_queryables(
41
79
  self,
@@ -92,23 +130,20 @@ class EsAsyncBaseFiltersClient(AsyncBaseFiltersClient):
92
130
  while stack:
93
131
  field_fqn, field_def = stack.popleft()
94
132
 
95
- # Iterate over nested fields
133
+ if self._is_excluded(field_fqn, excluded_fields):
134
+ continue
135
+
96
136
  field_properties = field_def.get("properties")
97
137
  if field_properties:
98
138
  stack.extend(
99
139
  (f"{field_fqn}.{k}", v)
100
140
  for k, v in field_properties.items()
101
141
  if v.get("enabled", True)
102
- and f"{field_fqn}.{k}" not in excluded_fields
103
142
  )
104
143
 
105
144
  # Skip non-indexed or disabled fields
106
145
  field_type = field_def.get("type")
107
- if (
108
- not field_type
109
- or not field_def.get("enabled", True)
110
- or field_fqn in excluded_fields
111
- ):
146
+ if not field_type or not field_def.get("enabled", True):
112
147
  continue
113
148
 
114
149
  # Fields in Item Properties should be exposed with their un-prefixed names,
@@ -22,7 +22,20 @@ def to_es_field(queryables_mapping: Dict[str, Any], field: str) -> str:
22
22
  Returns:
23
23
  str: The mapped field name suitable for Elasticsearch queries.
24
24
  """
25
- return queryables_mapping.get(field, field)
25
+ # First, try to find the field as-is in the mapping
26
+ if field in queryables_mapping:
27
+ return queryables_mapping[field]
28
+
29
+ # If field has 'properties.' prefix, try without it
30
+ # This handles cases where users specify 'properties.eo:cloud_cover'
31
+ # but queryables_mapping uses 'eo:cloud_cover' as the key
32
+ if field.startswith("properties."):
33
+ normalized_field = field[11:] # len("properties.") == 11
34
+ if normalized_field in queryables_mapping:
35
+ return queryables_mapping[normalized_field]
36
+
37
+ # If not found, return the original field
38
+ return field
26
39
 
27
40
 
28
41
  def to_es(queryables_mapping: Dict[str, Any], query: Dict[str, Any]) -> Dict[str, Any]:
@@ -160,6 +160,7 @@ ES_COLLECTIONS_MAPPINGS = {
160
160
  "dynamic_templates": ES_MAPPINGS_DYNAMIC_TEMPLATES,
161
161
  "properties": {
162
162
  "id": {"type": "keyword"},
163
+ "parent_ids": {"type": "keyword"},
163
164
  "bbox_shape": {"type": "geo_shape"},
164
165
  "extent.temporal.interval": {
165
166
  "type": "date",
@@ -12,7 +12,7 @@ from stac_fastapi.sfeos_helpers.mappings import ITEMS_INDEX_PREFIX
12
12
  class IndexCacheManager:
13
13
  """Manages caching of index aliases with expiration."""
14
14
 
15
- def __init__(self, cache_ttl_seconds: int = 3600):
15
+ def __init__(self, cache_ttl_seconds: int = 1800):
16
16
  """Initialize the cache manager.
17
17
 
18
18
  Args:
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "6.7.6"
2
+ __version__ = "6.8.1"
@@ -1,38 +0,0 @@
1
- """Mapping functions for Elasticsearch/OpenSearch.
2
-
3
- This module provides functions for working with Elasticsearch/OpenSearch mappings.
4
- """
5
-
6
- from typing import Any, Dict
7
-
8
-
9
- async def get_queryables_mapping_shared(
10
- mappings: Dict[str, Dict[str, Any]], collection_id: str = "*"
11
- ) -> Dict[str, str]:
12
- """Retrieve mapping of Queryables for search.
13
-
14
- Args:
15
- mappings (Dict[str, Dict[str, Any]]): The mapping information returned from
16
- Elasticsearch/OpenSearch client's indices.get_mapping() method.
17
- Expected structure is {index_name: {"mappings": {...}}}.
18
- collection_id (str, optional): The id of the Collection the Queryables
19
- belongs to. Defaults to "*".
20
-
21
- Returns:
22
- Dict[str, str]: A dictionary containing the Queryables mappings, where keys are
23
- field names and values are the corresponding paths in the Elasticsearch/OpenSearch
24
- document structure.
25
- """
26
- queryables_mapping = {}
27
-
28
- for mapping in mappings.values():
29
- fields = mapping["mappings"].get("properties", {})
30
- properties = fields.pop("properties", {}).get("properties", {}).keys()
31
-
32
- for field_key in fields:
33
- queryables_mapping[field_key] = field_key
34
-
35
- for property_key in properties:
36
- queryables_mapping[property_key] = f"properties.{property_key}"
37
-
38
- return queryables_mapping
File without changes