sfeos-helpers 6.0.0__py3-none-any.whl → 6.2.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.0.0.dist-info → sfeos_helpers-6.2.0.dist-info}/METADATA +92 -15
- sfeos_helpers-6.2.0.dist-info/RECORD +32 -0
- stac_fastapi/sfeos_helpers/aggregation/client.py +5 -2
- stac_fastapi/sfeos_helpers/database/__init__.py +5 -1
- stac_fastapi/sfeos_helpers/database/datetime.py +64 -3
- stac_fastapi/sfeos_helpers/database/index.py +59 -2
- stac_fastapi/sfeos_helpers/database/query.py +32 -0
- stac_fastapi/sfeos_helpers/search_engine/__init__.py +27 -0
- stac_fastapi/sfeos_helpers/search_engine/base.py +51 -0
- stac_fastapi/sfeos_helpers/search_engine/factory.py +36 -0
- stac_fastapi/sfeos_helpers/search_engine/index_operations.py +167 -0
- stac_fastapi/sfeos_helpers/search_engine/inserters.py +309 -0
- stac_fastapi/sfeos_helpers/search_engine/managers.py +198 -0
- stac_fastapi/sfeos_helpers/search_engine/selection/__init__.py +15 -0
- stac_fastapi/sfeos_helpers/search_engine/selection/base.py +30 -0
- stac_fastapi/sfeos_helpers/search_engine/selection/cache_manager.py +127 -0
- stac_fastapi/sfeos_helpers/search_engine/selection/factory.py +37 -0
- stac_fastapi/sfeos_helpers/search_engine/selection/selectors.py +129 -0
- stac_fastapi/sfeos_helpers/version.py +1 -1
- sfeos_helpers-6.0.0.dist-info/RECORD +0 -21
- {sfeos_helpers-6.0.0.dist-info → sfeos_helpers-6.2.0.dist-info}/WHEEL +0 -0
- {sfeos_helpers-6.0.0.dist-info → sfeos_helpers-6.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Base classes for index selection strategies."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseIndexSelector(ABC):
|
|
8
|
+
"""Base class for async index selectors."""
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
async def select_indexes(
|
|
12
|
+
self,
|
|
13
|
+
collection_ids: Optional[List[str]],
|
|
14
|
+
datetime_search: Dict[str, Optional[str]],
|
|
15
|
+
) -> str:
|
|
16
|
+
"""Select appropriate indexes asynchronously.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
collection_ids (Optional[List[str]]): List of collection IDs to filter by.
|
|
20
|
+
datetime_search (Dict[str, Optional[str]]): Datetime search criteria.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: Comma-separated string of selected index names.
|
|
24
|
+
"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
async def refresh_cache(self):
|
|
29
|
+
"""Refresh cache (no-op for unfiltered selector)."""
|
|
30
|
+
pass
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Cache management for index selection strategies."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from stac_fastapi.sfeos_helpers.database import index_alias_by_collection_id
|
|
9
|
+
from stac_fastapi.sfeos_helpers.mappings import ITEMS_INDEX_PREFIX
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IndexCacheManager:
|
|
13
|
+
"""Manages caching of index aliases with expiration."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, cache_ttl_seconds: int = 3600):
|
|
16
|
+
"""Initialize the cache manager.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
cache_ttl_seconds (int): Time-to-live for cache entries in seconds.
|
|
20
|
+
"""
|
|
21
|
+
self._cache: Optional[Dict[str, List[str]]] = None
|
|
22
|
+
self._timestamp: float = 0
|
|
23
|
+
self._ttl = cache_ttl_seconds
|
|
24
|
+
self._lock = threading.Lock()
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def is_expired(self) -> bool:
|
|
28
|
+
"""Check if the cache has expired.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
bool: True if cache is expired, False otherwise.
|
|
32
|
+
"""
|
|
33
|
+
return time.time() - self._timestamp > self._ttl
|
|
34
|
+
|
|
35
|
+
def get_cache(self) -> Optional[Dict[str, List[str]]]:
|
|
36
|
+
"""Get the current cache if not expired.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Optional[Dict[str, List[str]]]: Cache data if valid, None if expired.
|
|
40
|
+
"""
|
|
41
|
+
with self._lock:
|
|
42
|
+
if self.is_expired:
|
|
43
|
+
return None
|
|
44
|
+
return {k: v.copy() for k, v in self._cache.items()}
|
|
45
|
+
|
|
46
|
+
def set_cache(self, data: Dict[str, List[str]]) -> None:
|
|
47
|
+
"""Set cache data and update timestamp.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
data (Dict[str, List[str]]): Cache data to store.
|
|
51
|
+
"""
|
|
52
|
+
self._cache = data
|
|
53
|
+
self._timestamp = time.time()
|
|
54
|
+
|
|
55
|
+
def clear_cache(self) -> None:
|
|
56
|
+
"""Clear the cache and reset timestamp."""
|
|
57
|
+
self._cache = None
|
|
58
|
+
self._timestamp = 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class IndexAliasLoader:
|
|
62
|
+
"""Asynchronous loader for index aliases."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, client: Any, cache_manager: IndexCacheManager):
|
|
65
|
+
"""Initialize the async alias loader.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
client: Async search engine client instance.
|
|
69
|
+
cache_manager (IndexCacheManager): Cache manager instance.
|
|
70
|
+
"""
|
|
71
|
+
self.client = client
|
|
72
|
+
self.cache_manager = cache_manager
|
|
73
|
+
|
|
74
|
+
async def load_aliases(self) -> Dict[str, List[str]]:
|
|
75
|
+
"""Load index aliases from search engine.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Dict[str, List[str]]: Mapping of base aliases to item aliases.
|
|
79
|
+
"""
|
|
80
|
+
response = await self.client.indices.get_alias(index=f"{ITEMS_INDEX_PREFIX}*")
|
|
81
|
+
result = defaultdict(list)
|
|
82
|
+
for index_info in response.values():
|
|
83
|
+
aliases = index_info.get("aliases", {})
|
|
84
|
+
items_aliases = sorted(
|
|
85
|
+
[
|
|
86
|
+
alias
|
|
87
|
+
for alias in aliases.keys()
|
|
88
|
+
if alias.startswith(ITEMS_INDEX_PREFIX)
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if items_aliases:
|
|
93
|
+
result[items_aliases[0]].extend(items_aliases[1:])
|
|
94
|
+
|
|
95
|
+
self.cache_manager.set_cache(result)
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
async def get_aliases(self) -> Dict[str, List[str]]:
|
|
99
|
+
"""Get aliases from cache or load if expired.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dict[str, List[str]]: Alias mapping data.
|
|
103
|
+
"""
|
|
104
|
+
cached = self.cache_manager.get_cache()
|
|
105
|
+
if cached is not None:
|
|
106
|
+
return cached
|
|
107
|
+
return await self.load_aliases()
|
|
108
|
+
|
|
109
|
+
async def refresh_aliases(self) -> Dict[str, List[str]]:
|
|
110
|
+
"""Force refresh aliases from search engine.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Dict[str, List[str]]: Fresh alias mapping data.
|
|
114
|
+
"""
|
|
115
|
+
return await self.load_aliases()
|
|
116
|
+
|
|
117
|
+
async def get_collection_indexes(self, collection_id: str) -> List[str]:
|
|
118
|
+
"""Get all index aliases for a specific collection.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
collection_id (str): Collection identifier.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List[str]: List of index aliases for the collection.
|
|
125
|
+
"""
|
|
126
|
+
aliases = await self.get_aliases()
|
|
127
|
+
return aliases.get(index_alias_by_collection_id(collection_id), [])
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Factory for creating index selection strategies."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from stac_fastapi.core.utilities import get_bool_env
|
|
6
|
+
|
|
7
|
+
from .base import BaseIndexSelector
|
|
8
|
+
from .selectors import DatetimeBasedIndexSelector, UnfilteredIndexSelector
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class IndexSelectorFactory:
|
|
12
|
+
"""Factory class for creating index selector instances."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def create_selector(client: Any) -> BaseIndexSelector:
|
|
16
|
+
"""Create an appropriate asynchronous index selector based on environment configuration.
|
|
17
|
+
|
|
18
|
+
Checks the ENABLE_DATETIME_INDEX_FILTERING environment variable to determine
|
|
19
|
+
whether to use datetime-based filtering or return all available indices.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
client: Asynchronous Elasticsearch/OpenSearch client instance, used only if datetime
|
|
23
|
+
filtering is enabled.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
IndexSelectionStrategy: Either an AsyncDatetimeBasedIndexSelector if datetime
|
|
27
|
+
filtering is enabled, or an UnfilteredIndexSelector otherwise.
|
|
28
|
+
"""
|
|
29
|
+
use_datetime_filtering = get_bool_env(
|
|
30
|
+
"ENABLE_DATETIME_INDEX_FILTERING", default="false"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
DatetimeBasedIndexSelector(client)
|
|
35
|
+
if use_datetime_filtering
|
|
36
|
+
else UnfilteredIndexSelector()
|
|
37
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Async index selectors with datetime-based filtering."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from stac_fastapi.sfeos_helpers.database import filter_indexes_by_datetime
|
|
6
|
+
from stac_fastapi.sfeos_helpers.mappings import ITEM_INDICES
|
|
7
|
+
|
|
8
|
+
from ...database import indices
|
|
9
|
+
from .base import BaseIndexSelector
|
|
10
|
+
from .cache_manager import IndexAliasLoader, IndexCacheManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DatetimeBasedIndexSelector(BaseIndexSelector):
|
|
14
|
+
"""Asynchronous index selector that filters indices based on datetime criteria with caching."""
|
|
15
|
+
|
|
16
|
+
_instance = None
|
|
17
|
+
|
|
18
|
+
def __new__(cls, client):
|
|
19
|
+
"""Create singleton instance.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
client: Async search engine client instance.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
DatetimeBasedIndexSelector: Singleton instance.
|
|
26
|
+
"""
|
|
27
|
+
if cls._instance is None:
|
|
28
|
+
cls._instance = super().__new__(cls)
|
|
29
|
+
return cls._instance
|
|
30
|
+
|
|
31
|
+
def __init__(self, client: Any):
|
|
32
|
+
"""Initialize the datetime-based index selector.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
client: Elasticsearch/OpenSearch client instance used for querying
|
|
36
|
+
index aliases and metadata.
|
|
37
|
+
"""
|
|
38
|
+
if not hasattr(self, "_initialized"):
|
|
39
|
+
self.cache_manager = IndexCacheManager()
|
|
40
|
+
self.alias_loader = IndexAliasLoader(client, self.cache_manager)
|
|
41
|
+
self._initialized = True
|
|
42
|
+
|
|
43
|
+
async def refresh_cache(self) -> Dict[str, List[str]]:
|
|
44
|
+
"""Force refresh of the aliases cache.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Dict[str, List[str]]: Refreshed dictionary mapping base collection aliases
|
|
48
|
+
to lists of their corresponding item index aliases.
|
|
49
|
+
"""
|
|
50
|
+
return await self.alias_loader.refresh_aliases()
|
|
51
|
+
|
|
52
|
+
async def get_collection_indexes(self, collection_id: str) -> List[str]:
|
|
53
|
+
"""Get all index aliases for a specific collection.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
collection_id (str): The ID of the collection to retrieve indexes for.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
List[str]: List of index aliases associated with the collection.
|
|
60
|
+
Returns empty list if collection is not found in cache.
|
|
61
|
+
"""
|
|
62
|
+
return await self.alias_loader.get_collection_indexes(collection_id)
|
|
63
|
+
|
|
64
|
+
async def select_indexes(
|
|
65
|
+
self,
|
|
66
|
+
collection_ids: Optional[List[str]],
|
|
67
|
+
datetime_search: Dict[str, Optional[str]],
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Select indexes filtered by collection IDs and datetime criteria.
|
|
70
|
+
|
|
71
|
+
For each specified collection, retrieves its associated indexes and filters
|
|
72
|
+
them based on datetime range. If no collection IDs are provided, returns
|
|
73
|
+
all item indices.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
collection_ids (Optional[List[str]]): List of collection IDs to filter by.
|
|
77
|
+
If None or empty, returns all item indices.
|
|
78
|
+
datetime_search (Dict[str, Optional[str]]): Dictionary containing datetime
|
|
79
|
+
search criteria with 'gte' and 'lte' keys for range filtering.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
str: Comma-separated string of selected index names that match the
|
|
83
|
+
collection and datetime criteria. Returns empty string if no
|
|
84
|
+
indexes match the criteria.
|
|
85
|
+
"""
|
|
86
|
+
if collection_ids:
|
|
87
|
+
selected_indexes = []
|
|
88
|
+
for collection_id in collection_ids:
|
|
89
|
+
collection_indexes = await self.get_collection_indexes(collection_id)
|
|
90
|
+
filtered_indexes = filter_indexes_by_datetime(
|
|
91
|
+
collection_indexes,
|
|
92
|
+
datetime_search.get("gte"),
|
|
93
|
+
datetime_search.get("lte"),
|
|
94
|
+
)
|
|
95
|
+
selected_indexes.extend(filtered_indexes)
|
|
96
|
+
|
|
97
|
+
return ",".join(selected_indexes) if selected_indexes else ""
|
|
98
|
+
|
|
99
|
+
return ITEM_INDICES
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class UnfilteredIndexSelector(BaseIndexSelector):
|
|
103
|
+
"""Index selector that returns all available indices without filtering."""
|
|
104
|
+
|
|
105
|
+
async def select_indexes(
|
|
106
|
+
self,
|
|
107
|
+
collection_ids: Optional[List[str]],
|
|
108
|
+
datetime_search: Dict[str, Optional[str]],
|
|
109
|
+
) -> str:
|
|
110
|
+
"""Select all indices for given collections without datetime filtering.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
collection_ids (Optional[List[str]]): List of collection IDs to filter by.
|
|
114
|
+
If None, all collections are considered.
|
|
115
|
+
datetime_search (Dict[str, Optional[str]]): Datetime search criteria
|
|
116
|
+
(ignored by this implementation).
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
str: Comma-separated string of all available index names for the collections.
|
|
120
|
+
"""
|
|
121
|
+
return indices(collection_ids)
|
|
122
|
+
|
|
123
|
+
async def refresh_cache(self):
|
|
124
|
+
"""Refresh cache (no-op for unfiltered selector).
|
|
125
|
+
|
|
126
|
+
Note:
|
|
127
|
+
Unfiltered selector doesn't use cache, so this is a no-op operation.
|
|
128
|
+
"""
|
|
129
|
+
pass
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.2.0"
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
stac_fastapi/sfeos_helpers/mappings.py,sha256=z6GJFJUE7bRKF9ODc8_ddkb7JCOokMtj4p2LeaQqrQQ,8237
|
|
2
|
-
stac_fastapi/sfeos_helpers/version.py,sha256=Fo5UFEQVxJZ3nywa3IY-enu5UQBE0X45nrQaRBe8c9o,45
|
|
3
|
-
stac_fastapi/sfeos_helpers/aggregation/__init__.py,sha256=Mym17lFh90by1GnoQgMyIKAqRNJnvCgVSXDYzjBiPQk,1210
|
|
4
|
-
stac_fastapi/sfeos_helpers/aggregation/client.py,sha256=JCUVBXsUXHdUXn59WaytGxB-R6OLhC_LApPm-pzQNTI,17818
|
|
5
|
-
stac_fastapi/sfeos_helpers/aggregation/format.py,sha256=qUW1jjh2EEjy-V7riliFR77grpi-AgsTmP76z60K5Lo,2011
|
|
6
|
-
stac_fastapi/sfeos_helpers/database/__init__.py,sha256=WAZc7Djw9VZHt-crkFkhtILVCZb8GzIegS6CpdGA4gc,2477
|
|
7
|
-
stac_fastapi/sfeos_helpers/database/datetime.py,sha256=L_bY0EtRi6b8XIN9zcHbFe-O2jFdItkgkNvkQ4Z1jyY,2279
|
|
8
|
-
stac_fastapi/sfeos_helpers/database/document.py,sha256=LtjX15gvaOuZC_k2t_oQhys_c-zRTLN5rwX0hNJkHnM,1725
|
|
9
|
-
stac_fastapi/sfeos_helpers/database/index.py,sha256=7xFNzOQzFxBAtQUKUmhKcqz5j3_HRPuwIecdEEWb0i0,4476
|
|
10
|
-
stac_fastapi/sfeos_helpers/database/mapping.py,sha256=4-MSd4xH5wg7yoC4aPjzYMDSEvP026bw4k2TfffMT5E,1387
|
|
11
|
-
stac_fastapi/sfeos_helpers/database/query.py,sha256=3aFby56ggvTxtBg1vH1AXw2P2_CloPIV-cwL260P47E,2972
|
|
12
|
-
stac_fastapi/sfeos_helpers/database/utils.py,sha256=nPTWqP-pdSZumeYRx92XTN4xmmufRSqCDHaiSLdg-3o,7311
|
|
13
|
-
stac_fastapi/sfeos_helpers/filter/__init__.py,sha256=n3zL_MhEGOoxMz1KeijyK_UKiZ0MKPl90zHtYI5RAy8,1557
|
|
14
|
-
stac_fastapi/sfeos_helpers/filter/client.py,sha256=QwjYWXkevoVS7HPtoXfeSzDy-_GJnFhPJtJM49D14oU,4229
|
|
15
|
-
stac_fastapi/sfeos_helpers/filter/cql2.py,sha256=Cg9kRYD9CVkVSyRqOyB5oVXmlyteSn2bw88sqklGpUM,955
|
|
16
|
-
stac_fastapi/sfeos_helpers/filter/transform.py,sha256=1GEWQSp-rbq7_1nDVv1ApDbWxt8DswJWxwaxzV85gj4,4644
|
|
17
|
-
stac_fastapi/sfeos_helpers/models/patch.py,sha256=akzfF7vXI6AyGDrxgP8KqVwBvBDQayXYNNf4pm6Q8qM,4209
|
|
18
|
-
sfeos_helpers-6.0.0.dist-info/METADATA,sha256=huD7geSPsXMjM-KvE7KsaM6gTuGHRO7QPDBlpyTBayc,31173
|
|
19
|
-
sfeos_helpers-6.0.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
20
|
-
sfeos_helpers-6.0.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
|
|
21
|
-
sfeos_helpers-6.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|