sfeos-helpers 6.8.1__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.
@@ -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 collections import defaultdict
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 {k: v.copy() for k, v in self._cache.items()}
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._cache = data
53
- self._timestamp = time.time()
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._cache = None
58
- self._timestamp = 0
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 base aliases to item aliases.
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 = defaultdict(list)
82
- for index_info in response.values():
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
- result[items_aliases[0]].extend(items_aliases[1:])
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
- async def get_aliases(self) -> Dict[str, List[str]]:
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(self, collection_id: str) -> List[str]:
118
- """Get all index aliases for a specific collection.
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 index aliases for the collection.
180
+ List[Tuple[Dict[str, str]]]: List of tuples with alias dictionaries.
125
181
  """
126
182
  aliases = await self.get_aliases()
127
- return aliases.get(index_alias_by_collection_id(collection_id), [])
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.sfeos_helpers.database import filter_indexes_by_datetime
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
- async def refresh_cache(self) -> Dict[str, List[str]]:
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(self, collection_id: str) -> List[str]:
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: Dict[str, Optional[str]],
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 (Dict[str, Optional[str]]): Dictionary containing datetime
79
- search criteria with 'gte' and 'lte' keys for range filtering.
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: Dict[str, Optional[str]],
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 (Dict[str, Optional[str]]): Datetime search criteria
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.8.1"
2
+ __version__ = "6.10.0"