sfeos-helpers 6.9.0__py3-none-any.whl → 6.10.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.9.0.dist-info → sfeos_helpers-6.10.0.dist-info}/METADATA +2 -2
- {sfeos_helpers-6.9.0.dist-info → sfeos_helpers-6.10.0.dist-info}/RECORD +19 -18
- stac_fastapi/sfeos_helpers/aggregation/client.py +1 -3
- stac_fastapi/sfeos_helpers/database/__init__.py +20 -1
- stac_fastapi/sfeos_helpers/database/catalogs.py +190 -0
- stac_fastapi/sfeos_helpers/database/datetime.py +54 -1
- stac_fastapi/sfeos_helpers/database/index.py +87 -40
- stac_fastapi/sfeos_helpers/database/query.py +1 -1
- stac_fastapi/sfeos_helpers/database/utils.py +34 -2
- stac_fastapi/sfeos_helpers/mappings.py +2 -2
- stac_fastapi/sfeos_helpers/search_engine/base.py +30 -0
- stac_fastapi/sfeos_helpers/search_engine/index_operations.py +80 -25
- stac_fastapi/sfeos_helpers/search_engine/inserters.py +173 -95
- stac_fastapi/sfeos_helpers/search_engine/managers.py +340 -56
- stac_fastapi/sfeos_helpers/search_engine/selection/base.py +7 -3
- stac_fastapi/sfeos_helpers/search_engine/selection/cache_manager.py +82 -25
- stac_fastapi/sfeos_helpers/search_engine/selection/selectors.py +71 -14
- stac_fastapi/sfeos_helpers/version.py +1 -1
- {sfeos_helpers-6.9.0.dist-info → sfeos_helpers-6.10.0.dist-info}/WHEEL +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Cache management for index selection strategies."""
|
|
2
2
|
|
|
3
|
+
import copy
|
|
3
4
|
import threading
|
|
4
5
|
import time
|
|
5
|
-
from
|
|
6
|
-
from typing import Any, Dict, List, Optional
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
7
7
|
|
|
8
8
|
from stac_fastapi.sfeos_helpers.database import index_alias_by_collection_id
|
|
9
9
|
from stac_fastapi.sfeos_helpers.mappings import ITEMS_INDEX_PREFIX
|
|
@@ -18,7 +18,7 @@ class IndexCacheManager:
|
|
|
18
18
|
Args:
|
|
19
19
|
cache_ttl_seconds (int): Time-to-live for cache entries in seconds.
|
|
20
20
|
"""
|
|
21
|
-
self._cache: Optional[Dict[str, List[str]]] = None
|
|
21
|
+
self._cache: Optional[Dict[str, List[Tuple[Dict[str, str]]]]] = None
|
|
22
22
|
self._timestamp: float = 0
|
|
23
23
|
self._ttl = cache_ttl_seconds
|
|
24
24
|
self._lock = threading.Lock()
|
|
@@ -32,30 +32,32 @@ class IndexCacheManager:
|
|
|
32
32
|
"""
|
|
33
33
|
return time.time() - self._timestamp > self._ttl
|
|
34
34
|
|
|
35
|
-
def get_cache(self) -> Optional[Dict[str, List[str]]]:
|
|
35
|
+
def get_cache(self) -> Optional[Dict[str, List[Tuple[Dict[str, str]]]]]:
|
|
36
36
|
"""Get the current cache if not expired.
|
|
37
37
|
|
|
38
38
|
Returns:
|
|
39
|
-
Optional[Dict[str, List[str]]]: Cache data if valid, None if expired.
|
|
39
|
+
Optional[Dict[str, List[Tuple[Dict[str, str]]]]]: Cache data if valid, None if expired.
|
|
40
40
|
"""
|
|
41
41
|
with self._lock:
|
|
42
42
|
if self.is_expired:
|
|
43
43
|
return None
|
|
44
|
-
return
|
|
44
|
+
return copy.deepcopy(self._cache) if self._cache else None
|
|
45
45
|
|
|
46
|
-
def set_cache(self, data: Dict[str, List[str]]) -> None:
|
|
46
|
+
def set_cache(self, data: Dict[str, List[Tuple[Dict[str, str]]]]) -> None:
|
|
47
47
|
"""Set cache data and update timestamp.
|
|
48
48
|
|
|
49
49
|
Args:
|
|
50
|
-
data (Dict[str, List[str]]): Cache data to store.
|
|
50
|
+
data (Dict[str, List[Tuple[Dict[str, str]]]]): Cache data to store.
|
|
51
51
|
"""
|
|
52
|
-
self.
|
|
53
|
-
|
|
52
|
+
with self._lock:
|
|
53
|
+
self._cache = data
|
|
54
|
+
self._timestamp = time.time()
|
|
54
55
|
|
|
55
56
|
def clear_cache(self) -> None:
|
|
56
57
|
"""Clear the cache and reset timestamp."""
|
|
57
|
-
self.
|
|
58
|
-
|
|
58
|
+
with self._lock:
|
|
59
|
+
self._cache = None
|
|
60
|
+
self._timestamp = 0
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
class IndexAliasLoader:
|
|
@@ -71,15 +73,16 @@ class IndexAliasLoader:
|
|
|
71
73
|
self.client = client
|
|
72
74
|
self.cache_manager = cache_manager
|
|
73
75
|
|
|
74
|
-
async def load_aliases(self) -> Dict[str, List[str]]:
|
|
76
|
+
async def load_aliases(self) -> Dict[str, List[Tuple[Dict[str, str]]]]:
|
|
75
77
|
"""Load index aliases from search engine.
|
|
76
78
|
|
|
77
79
|
Returns:
|
|
78
|
-
Dict[str, List[str]]: Mapping of
|
|
80
|
+
Dict[str, List[Tuple[Dict[str, str]]]]: Mapping of main collection aliases to their data.
|
|
79
81
|
"""
|
|
80
82
|
response = await self.client.indices.get_alias(index=f"{ITEMS_INDEX_PREFIX}*")
|
|
81
|
-
result =
|
|
82
|
-
|
|
83
|
+
result: Dict[str, List[Tuple[Dict[str, str]]]] = {}
|
|
84
|
+
|
|
85
|
+
for index_name, index_info in response.items():
|
|
83
86
|
aliases = index_info.get("aliases", {})
|
|
84
87
|
items_aliases = sorted(
|
|
85
88
|
[
|
|
@@ -90,38 +93,92 @@ class IndexAliasLoader:
|
|
|
90
93
|
)
|
|
91
94
|
|
|
92
95
|
if items_aliases:
|
|
93
|
-
|
|
96
|
+
main_alias = self._find_main_alias(items_aliases)
|
|
97
|
+
aliases_dict = self._organize_aliases(items_aliases, main_alias)
|
|
98
|
+
|
|
99
|
+
if aliases_dict:
|
|
100
|
+
if main_alias not in result:
|
|
101
|
+
result[main_alias] = []
|
|
102
|
+
|
|
103
|
+
result[main_alias].append((aliases_dict,))
|
|
94
104
|
|
|
95
105
|
self.cache_manager.set_cache(result)
|
|
96
106
|
return result
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _find_main_alias(aliases: List[str]) -> str:
|
|
110
|
+
"""Find the main collection alias (without temporal suffixes).
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
aliases (List[str]): List of all aliases for an index.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
str: The main collection alias.
|
|
117
|
+
"""
|
|
118
|
+
temporal_keywords = ["datetime", "start_datetime", "end_datetime"]
|
|
119
|
+
|
|
120
|
+
for alias in aliases:
|
|
121
|
+
if not any(keyword in alias for keyword in temporal_keywords):
|
|
122
|
+
return alias
|
|
123
|
+
|
|
124
|
+
return aliases[0]
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _organize_aliases(aliases: List[str], main_alias: str) -> Dict[str, str]:
|
|
128
|
+
"""Organize temporal aliases into a dictionary with type as key.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
aliases (List[str]): All aliases for the index.
|
|
132
|
+
main_alias (str): The main collection alias.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dict[str, str]: Dictionary with datetime types as keys and alias names as values.
|
|
136
|
+
"""
|
|
137
|
+
aliases_dict = {}
|
|
138
|
+
|
|
139
|
+
for alias in aliases:
|
|
140
|
+
if alias == main_alias:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
if "start_datetime" in alias:
|
|
144
|
+
aliases_dict["start_datetime"] = alias
|
|
145
|
+
elif "end_datetime" in alias:
|
|
146
|
+
aliases_dict["end_datetime"] = alias
|
|
147
|
+
elif "datetime" in alias:
|
|
148
|
+
aliases_dict["datetime"] = alias
|
|
149
|
+
|
|
150
|
+
return aliases_dict
|
|
151
|
+
|
|
152
|
+
async def get_aliases(self) -> Dict[str, List[Tuple[Dict[str, str]]]]:
|
|
99
153
|
"""Get aliases from cache or load if expired.
|
|
100
154
|
|
|
101
155
|
Returns:
|
|
102
|
-
Dict[str, List[str]]: Alias mapping data.
|
|
156
|
+
Dict[str, List[Tuple[Dict[str, str]]]]: Alias mapping data.
|
|
103
157
|
"""
|
|
104
158
|
cached = self.cache_manager.get_cache()
|
|
105
159
|
if cached is not None:
|
|
106
160
|
return cached
|
|
107
161
|
return await self.load_aliases()
|
|
108
162
|
|
|
109
|
-
async def refresh_aliases(self) -> Dict[str, List[str]]:
|
|
163
|
+
async def refresh_aliases(self) -> Dict[str, List[Tuple[Dict[str, str]]]]:
|
|
110
164
|
"""Force refresh aliases from search engine.
|
|
111
165
|
|
|
112
166
|
Returns:
|
|
113
|
-
Dict[str, List[str]]: Fresh alias mapping data.
|
|
167
|
+
Dict[str, List[Tuple[Dict[str, str]]]]: Fresh alias mapping data.
|
|
114
168
|
"""
|
|
115
169
|
return await self.load_aliases()
|
|
116
170
|
|
|
117
|
-
async def get_collection_indexes(
|
|
118
|
-
|
|
171
|
+
async def get_collection_indexes(
|
|
172
|
+
self, collection_id: str
|
|
173
|
+
) -> List[Tuple[Dict[str, str]]]:
|
|
174
|
+
"""Get index information for a specific collection.
|
|
119
175
|
|
|
120
176
|
Args:
|
|
121
177
|
collection_id (str): Collection identifier.
|
|
122
178
|
|
|
123
179
|
Returns:
|
|
124
|
-
List[str]: List of
|
|
180
|
+
List[Tuple[Dict[str, str]]]: List of tuples with alias dictionaries.
|
|
125
181
|
"""
|
|
126
182
|
aliases = await self.get_aliases()
|
|
127
|
-
|
|
183
|
+
main_alias = index_alias_by_collection_id(collection_id)
|
|
184
|
+
return aliases.get(main_alias, [])
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Async index selectors with datetime-based filtering."""
|
|
2
2
|
|
|
3
|
-
from typing import Any, Dict, List, Optional
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
|
-
from stac_fastapi.
|
|
5
|
+
from stac_fastapi.core.utilities import get_bool_env
|
|
6
|
+
from stac_fastapi.sfeos_helpers.database import filter_indexes_by_datetime, return_date
|
|
6
7
|
from stac_fastapi.sfeos_helpers.mappings import ITEM_INDICES
|
|
7
8
|
|
|
8
9
|
from ...database import indices
|
|
@@ -40,23 +41,30 @@ class DatetimeBasedIndexSelector(BaseIndexSelector):
|
|
|
40
41
|
self.alias_loader = IndexAliasLoader(client, self.cache_manager)
|
|
41
42
|
self._initialized = True
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
@property
|
|
45
|
+
def use_datetime(self) -> bool:
|
|
46
|
+
"""Get USE_DATETIME setting dynamically."""
|
|
47
|
+
return get_bool_env("USE_DATETIME", default=True)
|
|
48
|
+
|
|
49
|
+
async def refresh_cache(self) -> Dict[str, List[Tuple[Dict[str, str]]]]:
|
|
44
50
|
"""Force refresh of the aliases cache.
|
|
45
51
|
|
|
46
52
|
Returns:
|
|
47
|
-
Dict[str, List[str]]: Refreshed dictionary mapping base collection aliases
|
|
53
|
+
Dict[str, List[Tuple[Dict[str, str]]]]: Refreshed dictionary mapping base collection aliases
|
|
48
54
|
to lists of their corresponding item index aliases.
|
|
49
55
|
"""
|
|
50
56
|
return await self.alias_loader.refresh_aliases()
|
|
51
57
|
|
|
52
|
-
async def get_collection_indexes(
|
|
58
|
+
async def get_collection_indexes(
|
|
59
|
+
self, collection_id: str
|
|
60
|
+
) -> List[tuple[dict[str, str]]]:
|
|
53
61
|
"""Get all index aliases for a specific collection.
|
|
54
62
|
|
|
55
63
|
Args:
|
|
56
64
|
collection_id (str): The ID of the collection to retrieve indexes for.
|
|
57
65
|
|
|
58
66
|
Returns:
|
|
59
|
-
List[str]: List of index aliases associated with the collection.
|
|
67
|
+
List[tuple[dict[str, str]]]: List of index aliases associated with the collection.
|
|
60
68
|
Returns empty list if collection is not found in cache.
|
|
61
69
|
"""
|
|
62
70
|
return await self.alias_loader.get_collection_indexes(collection_id)
|
|
@@ -64,7 +72,8 @@ class DatetimeBasedIndexSelector(BaseIndexSelector):
|
|
|
64
72
|
async def select_indexes(
|
|
65
73
|
self,
|
|
66
74
|
collection_ids: Optional[List[str]],
|
|
67
|
-
datetime_search:
|
|
75
|
+
datetime_search: str,
|
|
76
|
+
for_insertion: bool = False,
|
|
68
77
|
) -> str:
|
|
69
78
|
"""Select indexes filtered by collection IDs and datetime criteria.
|
|
70
79
|
|
|
@@ -75,22 +84,23 @@ class DatetimeBasedIndexSelector(BaseIndexSelector):
|
|
|
75
84
|
Args:
|
|
76
85
|
collection_ids (Optional[List[str]]): List of collection IDs to filter by.
|
|
77
86
|
If None or empty, returns all item indices.
|
|
78
|
-
datetime_search (
|
|
79
|
-
|
|
87
|
+
datetime_search (str): Datetime search criteria.
|
|
88
|
+
for_insertion (bool): If True, selects indexes for inserting items into
|
|
89
|
+
the database. If False, selects indexes for searching/querying items.
|
|
90
|
+
Defaults to False (search mode).
|
|
80
91
|
|
|
81
92
|
Returns:
|
|
82
93
|
str: Comma-separated string of selected index names that match the
|
|
83
94
|
collection and datetime criteria. Returns empty string if no
|
|
84
95
|
indexes match the criteria.
|
|
85
96
|
"""
|
|
97
|
+
datetime_filters = self.parse_datetime_filters(datetime_search, for_insertion)
|
|
86
98
|
if collection_ids:
|
|
87
99
|
selected_indexes = []
|
|
88
100
|
for collection_id in collection_ids:
|
|
89
101
|
collection_indexes = await self.get_collection_indexes(collection_id)
|
|
90
102
|
filtered_indexes = filter_indexes_by_datetime(
|
|
91
|
-
collection_indexes,
|
|
92
|
-
datetime_search.get("gte"),
|
|
93
|
-
datetime_search.get("lte"),
|
|
103
|
+
collection_indexes, datetime_filters, self.use_datetime
|
|
94
104
|
)
|
|
95
105
|
selected_indexes.extend(filtered_indexes)
|
|
96
106
|
|
|
@@ -98,6 +108,49 @@ class DatetimeBasedIndexSelector(BaseIndexSelector):
|
|
|
98
108
|
|
|
99
109
|
return ITEM_INDICES
|
|
100
110
|
|
|
111
|
+
def parse_datetime_filters(
|
|
112
|
+
self, datetime: str, for_insertion: bool
|
|
113
|
+
) -> Dict[str, Dict[str, Optional[str]]]:
|
|
114
|
+
"""Parse datetime string into structured filter criteria.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
datetime: Datetime search criteria string
|
|
118
|
+
for_insertion (bool): If True, generates filters for inserting items.
|
|
119
|
+
If False, generates filters for searching items. Defaults to False.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Dictionary with datetime, start_datetime, and end_datetime filters
|
|
123
|
+
"""
|
|
124
|
+
parsed_datetime = return_date(datetime)
|
|
125
|
+
|
|
126
|
+
if for_insertion:
|
|
127
|
+
return {
|
|
128
|
+
"datetime": {
|
|
129
|
+
"gte": datetime if self.use_datetime else None,
|
|
130
|
+
"lte": datetime if self.use_datetime else None,
|
|
131
|
+
},
|
|
132
|
+
"start_datetime": {
|
|
133
|
+
"gte": datetime if not self.use_datetime else None,
|
|
134
|
+
"lte": datetime if not self.use_datetime else None,
|
|
135
|
+
},
|
|
136
|
+
"end_datetime": {"gte": None, "lte": None},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
"datetime": {
|
|
141
|
+
"gte": parsed_datetime.get("gte") if self.use_datetime else None,
|
|
142
|
+
"lte": parsed_datetime.get("lte") if self.use_datetime else None,
|
|
143
|
+
},
|
|
144
|
+
"start_datetime": {
|
|
145
|
+
"gte": parsed_datetime.get("gte") if not self.use_datetime else None,
|
|
146
|
+
"lte": None,
|
|
147
|
+
},
|
|
148
|
+
"end_datetime": {
|
|
149
|
+
"gte": None,
|
|
150
|
+
"lte": parsed_datetime.get("lte") if not self.use_datetime else None,
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
|
|
101
154
|
|
|
102
155
|
class UnfilteredIndexSelector(BaseIndexSelector):
|
|
103
156
|
"""Index selector that returns all available indices without filtering."""
|
|
@@ -105,15 +158,19 @@ class UnfilteredIndexSelector(BaseIndexSelector):
|
|
|
105
158
|
async def select_indexes(
|
|
106
159
|
self,
|
|
107
160
|
collection_ids: Optional[List[str]],
|
|
108
|
-
datetime_search:
|
|
161
|
+
datetime_search: str,
|
|
162
|
+
for_insertion: bool = False,
|
|
109
163
|
) -> str:
|
|
110
164
|
"""Select all indices for given collections without datetime filtering.
|
|
111
165
|
|
|
112
166
|
Args:
|
|
113
167
|
collection_ids (Optional[List[str]]): List of collection IDs to filter by.
|
|
114
168
|
If None, all collections are considered.
|
|
115
|
-
datetime_search (
|
|
169
|
+
datetime_search (str): Datetime search criteria
|
|
116
170
|
(ignored by this implementation).
|
|
171
|
+
for_insertion (bool): If True, selects indexes for inserting items into
|
|
172
|
+
the database. If False, selects indexes for searching/querying items.
|
|
173
|
+
Defaults to False (search mode).
|
|
117
174
|
|
|
118
175
|
Returns:
|
|
119
176
|
str: Comma-separated string of all available index names for the collections.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "6.
|
|
2
|
+
__version__ = "6.10.0"
|
|
File without changes
|