Skip to content

Commit 798a2c5

Browse files
committed
Add additional temporal aliases
1 parent ed2fd8d commit 798a2c5

File tree

17 files changed

+996
-202
lines changed

17 files changed

+996
-202
lines changed

stac_fastapi/core/stac_fastapi/core/core.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -510,18 +510,20 @@ async def post_all_collections(
510510
return await self.all_collections(
511511
limit=search_request.limit if hasattr(search_request, "limit") else None,
512512
bbox=search_request.bbox if hasattr(search_request, "bbox") else None,
513-
datetime=search_request.datetime
514-
if hasattr(search_request, "datetime")
515-
else None,
513+
datetime=(
514+
search_request.datetime if hasattr(search_request, "datetime") else None
515+
),
516516
token=search_request.token if hasattr(search_request, "token") else None,
517517
fields=fields,
518518
sortby=sortby,
519-
filter_expr=search_request.filter
520-
if hasattr(search_request, "filter")
521-
else None,
522-
filter_lang=search_request.filter_lang
523-
if hasattr(search_request, "filter_lang")
524-
else None,
519+
filter_expr=(
520+
search_request.filter if hasattr(search_request, "filter") else None
521+
),
522+
filter_lang=(
523+
search_request.filter_lang
524+
if hasattr(search_request, "filter_lang")
525+
else None
526+
),
525527
query=search_request.query if hasattr(search_request, "query") else None,
526528
q=search_request.q if hasattr(search_request, "q") else None,
527529
request=request,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
"""library version."""
2+
23
__version__ = "6.7.4"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
"""library version."""
2+
23
__version__ = "6.7.4"

stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,6 @@ def apply_datetime_filter(
494494
if not datetime_search:
495495
return search, result_metadata
496496

497-
498497
if USE_DATETIME:
499498
if "eq" in datetime_search:
500499
# For exact matches, include:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
"""library version."""
2+
23
__version__ = "6.7.4"

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/database/datetime.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def return_date(
138138
return result
139139

140140

141-
def extract_date(date_str: str | None) -> date | None:
141+
def extract_date(date_str: str) -> date:
142142
"""Extract date from ISO format string.
143143
144144
Args:
@@ -147,9 +147,6 @@ def extract_date(date_str: str | None) -> date | None:
147147
Returns:
148148
A date object extracted from the input string or None.
149149
"""
150-
if not date_str:
151-
return None
152-
153150
date_str = date_str.replace("Z", "+00:00")
154151
return datetime_type.fromisoformat(date_str).date()
155152

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/database/index.py

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
"""
55

66
import re
7-
from datetime import datetime
7+
from datetime import date, datetime
88
from functools import lru_cache
9-
from typing import Any, List, Optional, Tuple, Dict
10-
11-
from dateutil.parser import parse # type: ignore[import]
9+
from typing import Any, Dict, List, Optional, Tuple
1210

1311
from stac_fastapi.sfeos_helpers.mappings import (
1412
_ES_INDEX_NAME_UNSUPPORTED_CHARS_TABLE,
@@ -72,37 +70,59 @@ def indices(collection_ids: Optional[List[str]]) -> str:
7270

7371
def filter_indexes_by_datetime(
7472
collection_indexes: List[Tuple[Dict[str, str], ...]],
75-
datetime_search: Dict[str, Dict[str, str | None]]
73+
datetime_search: Dict[str, Dict[str, str | None]],
7674
) -> List[str]:
75+
"""
76+
Filter Elasticsearch index aliases based on datetime search criteria.
77+
78+
Filters a list of collection indexes by matching their datetime, start_datetime, and end_datetime
79+
aliases against the provided search criteria. Each criterion can have optional 'gte' (greater than
80+
or equal) and 'lte' (less than or equal) bounds.
81+
82+
Args:
83+
collection_indexes (List[Tuple[Dict[str, str], ...]]): A list of tuples containing dictionaries
84+
with 'datetime', 'start_datetime', and 'end_datetime' aliases.
85+
datetime_search (Dict[str, Dict[str, str | None]]): A dictionary with keys 'datetime',
86+
'start_datetime', and 'end_datetime', each containing 'gte' and 'lte' criteria as ISO format
87+
datetime strings or None.
88+
89+
Returns:
90+
List[str]: A list of start_datetime aliases that match all provided search criteria.
91+
"""
92+
7793
def extract_date_from_alias(alias: str) -> tuple[datetime, datetime] | None:
78-
date_pattern = re.compile(r'\d{4}-\d{2}-\d{2}')
94+
date_pattern = re.compile(r"\d{4}-\d{2}-\d{2}")
7995
try:
8096
dates = date_pattern.findall(alias)
8197

8298
if not dates:
8399
return None
84100

85101
if len(dates) >= 2:
86-
return datetime.strptime(dates[-2], '%Y-%m-%d'), datetime.strptime(dates[-1], '%Y-%m-%d')
102+
return datetime.strptime(dates[-2], "%Y-%m-%d"), datetime.strptime(
103+
dates[-1], "%Y-%m-%d"
104+
)
87105
else:
88-
date = datetime.strptime(dates[-1], '%Y-%m-%d')
106+
date = datetime.strptime(dates[-1], "%Y-%m-%d")
89107
return date, date
90108
except (ValueError, IndexError):
91109
return None
92110

93-
def parse_search_date(date_str: str | None) -> Optional[datetime.date]:
111+
def parse_search_date(date_str: str | None) -> Optional[date]:
94112
if not date_str:
95113
return None
96-
return datetime.fromisoformat(date_str.replace('Z', '+00:00')).date()
114+
date_str = date_str.rstrip("Z")
115+
return datetime.fromisoformat(date_str).date()
97116

98-
def check_criteria(value_begin: datetime.date, value_end: datetime.date, criteria: Dict) -> bool:
99-
gte = parse_search_date(criteria.get('gte'))
100-
lte = parse_search_date(criteria.get('lte'))
117+
def check_criteria(value_begin: date, value_end: date, criteria: Dict) -> bool:
118+
gte = parse_search_date(criteria.get("gte"))
119+
lte = parse_search_date(criteria.get("lte"))
101120

102-
if gte and value_begin.date() < gte:
121+
if gte and value_end < gte:
103122
return False
104-
if lte and value_end.date() > lte:
123+
if lte and value_begin > lte:
105124
return False
125+
106126
return True
107127

108128
filtered_indexes = []
@@ -112,9 +132,9 @@ def check_criteria(value_begin: datetime.date, value_end: datetime.date, criteri
112132
continue
113133

114134
index_dict = index_tuple[0]
115-
start_datetime_alias = index_dict.get('start_datetime')
116-
end_datetime_alias = index_dict.get('end_datetime')
117-
datetime_alias = index_dict.get('datetime')
135+
start_datetime_alias = index_dict.get("start_datetime")
136+
end_datetime_alias = index_dict.get("end_datetime")
137+
datetime_alias = index_dict.get("datetime")
118138

119139
if not start_datetime_alias:
120140
continue
@@ -123,15 +143,20 @@ def check_criteria(value_begin: datetime.date, value_end: datetime.date, criteri
123143
end_date = extract_date_from_alias(end_datetime_alias)
124144
datetime_date = extract_date_from_alias(datetime_alias)
125145

126-
if not start_range or not end_date or not datetime_date:
127-
continue
128-
129-
if not check_criteria(start_range[0], start_range[1], datetime_search.get('start_datetime', {})):
130-
continue
131-
if not check_criteria(end_date[0], end_date[1], datetime_search.get('end_datetime', {})):
132-
continue
133-
if not check_criteria(datetime_date[0], datetime_date[1], datetime_search.get('datetime', {})):
146+
if not check_criteria(
147+
start_range[0], start_range[1], datetime_search.get("start_datetime", {})
148+
):
134149
continue
150+
if end_date:
151+
if not check_criteria(
152+
end_date[0], end_date[1], datetime_search.get("end_datetime", {})
153+
):
154+
continue
155+
if datetime_date:
156+
if not check_criteria(
157+
datetime_date[0], datetime_date[1], datetime_search.get("datetime", {})
158+
):
159+
continue
135160

136161
filtered_indexes.append(start_datetime_alias)
137162

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/database/query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def apply_collections_datetime_filter_shared(
130130

131131

132132
def apply_collections_bbox_filter_shared(
133-
bbox: Union[str, List[float], None]
133+
bbox: Union[str, List[float], None],
134134
) -> Optional[Dict[str, Dict]]:
135135
"""Create a geo_shape filter for collections bbox search.
136136

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/search_engine/index_operations.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Search engine adapters for different implementations."""
22

33
import uuid
4-
from typing import Any, Dict, Literal, List
4+
from typing import Any, Dict, List, Literal
55

66
from stac_fastapi.sfeos_helpers.database import (
77
index_alias_by_collection_id,
@@ -39,27 +39,36 @@ async def create_simple_index(self, client: Any, collection_id: str) -> str:
3939
return index_name
4040

4141
async def create_datetime_index(
42-
self, client: Any, collection_id: str, start_datetime: str, datetime: str | None, end_datetime: str
42+
self,
43+
client: Any,
44+
collection_id: str,
45+
start_datetime: str,
46+
datetime: str,
47+
end_datetime: str,
4348
) -> str:
4449
"""Create a datetime-based index for the given collection.
4550
4651
Args:
4752
client: Search engine client instance.
4853
collection_id (str): Collection identifier.
4954
start_datetime (str): Start datetime for the index alias.
50-
datetime (str | None): Datetime for the datetime alias (can be None).
55+
datetime (str): Datetime for the datetime alias (can be None).
5156
end_datetime (str): End datetime for the index alias.
5257
5358
Returns:
5459
str: Created start_datetime alias name.
5560
"""
5661
index_name = self.create_index_name(collection_id)
57-
alias_start_date = self.create_alias_name(collection_id, "start_datetime", start_datetime)
62+
alias_start_date = self.create_alias_name(
63+
collection_id, "start_datetime", start_datetime
64+
)
5865
alias_date = self.create_alias_name(collection_id, "datetime", datetime)
59-
alias_end_date = self.create_alias_name(collection_id, "end_datetime", end_datetime)
66+
alias_end_date = self.create_alias_name(
67+
collection_id, "end_datetime", end_datetime
68+
)
6069
collection_alias = index_alias_by_collection_id(collection_id)
6170

62-
aliases = {
71+
aliases: Dict[str, Any] = {
6372
collection_alias: {},
6473
alias_start_date: {},
6574
alias_date: {},
@@ -96,7 +105,12 @@ async def update_index_alias(client: Any, end_date: str, old_alias: str) -> str:
96105
return new_alias
97106

98107
@staticmethod
99-
async def change_alias_name(client: Any, old_start_datetime_alias: str, aliases_to_change: List[str], aliases_to_create: List[str]) -> None:
108+
async def change_alias_name(
109+
client: Any,
110+
old_start_datetime_alias: str,
111+
aliases_to_change: List[str],
112+
aliases_to_create: List[str],
113+
) -> None:
100114
"""Change alias names by removing old aliases and adding new ones.
101115
102116
Args:
@@ -135,7 +149,9 @@ def create_index_name(collection_id: str) -> str:
135149

136150
@staticmethod
137151
def create_alias_name(
138-
collection_id: str, name: Literal["start_datetime", "datetime", "end_datetime"], start_date: str
152+
collection_id: str,
153+
name: Literal["start_datetime", "datetime", "end_datetime"],
154+
start_date: str,
139155
) -> str:
140156
"""Create alias name from collection ID and date.
141157
@@ -180,7 +196,11 @@ async def find_latest_item_in_index(client: Any, index_name: str) -> dict[str, A
180196
query = {
181197
"size": 1,
182198
"sort": [{"properties.start_datetime": {"order": "desc"}}],
183-
"_source": ["properties.start_datetime", "properties.datetime", "properties.end_datetime"],
199+
"_source": [
200+
"properties.start_datetime",
201+
"properties.datetime",
202+
"properties.end_datetime",
203+
],
184204
}
185205

186206
response = await client.search(index=index_name, body=query)

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/search_engine/inserters.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Async index insertion strategies."""
2+
23
import logging
3-
from typing import Any, Dict, List
4+
from typing import Any, Dict, List, Optional
45

56
from fastapi import HTTPException, status
67

@@ -117,7 +118,7 @@ async def _get_target_index_internal(
117118
collection_id: str,
118119
product: Dict[str, Any],
119120
check_size: bool = True,
120-
) -> str:
121+
) -> Optional[str]:
121122
"""Get target index with size checking internally.
122123
123124
Args:
@@ -132,9 +133,9 @@ async def _get_target_index_internal(
132133
product_datetimes = self.datetime_manager.validate_product_datetimes(product)
133134

134135
datetime_ranges = {
135-
"start_datetime": {"gte": product_datetimes.start_datetime, "lte": product_datetimes.start_datetime},
136-
"end_datetime": {"gte": product_datetimes.end_datetime, "lte": product_datetimes.end_datetime},
137-
"datetime": {"gte": product_datetimes.datetime, "lte": product_datetimes.datetime}
136+
"start_datetime": {"gte": None, "lte": product_datetimes.start_datetime},
137+
"end_datetime": {"gte": None, "lte": None},
138+
"datetime": {"gte": None, "lte": None},
138139
}
139140

140141
target_index = await index_selector.select_indexes(
@@ -149,7 +150,7 @@ async def _get_target_index_internal(
149150
await index_selector.refresh_cache()
150151
return target_index
151152

152-
all_indexes = sorted(all_indexes, key=lambda x: x[0]['start_datetime'])
153+
all_indexes = sorted(all_indexes, key=lambda x: x[0]["start_datetime"])
153154
start_date = extract_date(product_datetimes.start_datetime)
154155
end_date = extract_first_date_from_index(all_indexes[0][0]["start_datetime"])
155156

@@ -160,7 +161,7 @@ async def _get_target_index_internal(
160161
await index_selector.refresh_cache()
161162
return alias
162163

163-
if target_index != all_indexes[-1]:
164+
if target_index != all_indexes[-1][0]["start_datetime"]:
164165
for item in all_indexes:
165166
aliases_dict = item[0]
166167
if target_index in aliases_dict.values():
@@ -179,8 +180,16 @@ async def _get_target_index_internal(
179180
collection_id, product_datetimes, aliases_dict
180181
)
181182
await index_selector.refresh_cache()
183+
return target_index
182184

183-
return target_index
185+
for item in all_indexes:
186+
aliases_dict = item[0]
187+
if target_index in aliases_dict.values():
188+
await self.datetime_manager.handle_early_date(
189+
collection_id, product_datetimes, aliases_dict
190+
)
191+
return target_index
192+
return None
184193

185194
async def _ensure_indexes_exist(
186195
self, index_selector, collection_id: str, items: List[Dict[str, Any]]
@@ -199,9 +208,9 @@ async def _ensure_indexes_exist(
199208
await self.index_operations.create_datetime_index(
200209
self.client,
201210
collection_id,
202-
extract_date(first_item["properties"]["start_datetime"]),
203-
extract_date(first_item["properties"]["datetime"]),
204-
extract_date(first_item["properties"]["end_datetime"]),
211+
str(extract_date(first_item["properties"]["start_datetime"])),
212+
str(extract_date(first_item["properties"]["datetime"])),
213+
str(extract_date(first_item["properties"]["end_datetime"])),
205214
)
206215
await index_selector.refresh_cache()
207216

@@ -227,9 +236,9 @@ async def _check_and_handle_oversized_index(
227236
)
228237

229238
all_indexes = await index_selector.get_collection_indexes(collection_id)
230-
all_indexes = sorted(all_indexes, key=lambda x: x[0]['start_datetime'])
239+
all_indexes = sorted(all_indexes, key=lambda x: x[0]["start_datetime"])
231240

232-
latest_index = all_indexes[-1][0]
241+
latest_index = all_indexes[-1][0]["start_datetime"]
233242

234243
if first_item_index != latest_index:
235244
return None
@@ -248,7 +257,9 @@ async def _check_and_handle_oversized_index(
248257
end_datetime=latest_item["_source"]["properties"]["end_datetime"],
249258
)
250259

251-
await self.datetime_manager.handle_oversized_index(collection_id, product_datetimes, latest_index)
260+
await self.datetime_manager.handle_oversized_index(
261+
collection_id, product_datetimes, all_indexes[-1][0]
262+
)
252263
await index_selector.refresh_cache()
253264

254265

0 commit comments

Comments
 (0)