stac-fastapi-core 6.7.5__py3-none-any.whl → 6.8.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.
@@ -138,3 +138,43 @@ class BaseDatabaseLogic(abc.ABC):
138
138
  ) -> None:
139
139
  """Delete a collection from the database."""
140
140
  pass
141
+
142
+ @abc.abstractmethod
143
+ async def get_queryables_mapping(self, collection_id: str = "*") -> Dict[str, Any]:
144
+ """Retrieve mapping of Queryables for search."""
145
+ pass
146
+
147
+ async def get_all_catalogs(
148
+ self,
149
+ token: Optional[str],
150
+ limit: int,
151
+ request: Any = None,
152
+ sort: Optional[List[Dict[str, Any]]] = None,
153
+ ) -> Tuple[List[Dict[str, Any]], Optional[str], Optional[int]]:
154
+ """Retrieve a list of catalogs from the database, supporting pagination.
155
+
156
+ Args:
157
+ token (Optional[str]): The pagination token.
158
+ limit (int): The number of results to return.
159
+ request (Any, optional): The FastAPI request object. Defaults to None.
160
+ sort (Optional[List[Dict[str, Any]]], optional): Optional sort parameter. Defaults to None.
161
+
162
+ Returns:
163
+ A tuple of (catalogs, next pagination token if any, optional count).
164
+ """
165
+ pass
166
+
167
+ @abc.abstractmethod
168
+ async def create_catalog(self, catalog: Dict, refresh: bool = False) -> None:
169
+ """Create a catalog in the database."""
170
+ pass
171
+
172
+ @abc.abstractmethod
173
+ async def find_catalog(self, catalog_id: str) -> Dict:
174
+ """Find a catalog in the database."""
175
+ pass
176
+
177
+ @abc.abstractmethod
178
+ async def delete_catalog(self, catalog_id: str, refresh: bool = False) -> None:
179
+ """Delete a catalog from the database."""
180
+ pass
stac_fastapi/core/core.py CHANGED
@@ -24,8 +24,15 @@ from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
24
24
  from stac_fastapi.core.base_settings import ApiBaseSettings
25
25
  from stac_fastapi.core.datetime_utils import format_datetime_range
26
26
  from stac_fastapi.core.models.links import PagingLinks
27
- from stac_fastapi.core.redis_utils import redis_pagination_links
28
- from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
27
+ from stac_fastapi.core.queryables import (
28
+ QueryablesCache,
29
+ get_properties_from_cql2_filter,
30
+ )
31
+ from stac_fastapi.core.serializers import (
32
+ CatalogSerializer,
33
+ CollectionSerializer,
34
+ ItemSerializer,
35
+ )
29
36
  from stac_fastapi.core.session import Session
30
37
  from stac_fastapi.core.utilities import filter_fields, get_bool_env
31
38
  from stac_fastapi.extensions.core.transaction import AsyncBaseTransactionsClient
@@ -82,12 +89,28 @@ class CoreClient(AsyncBaseCoreClient):
82
89
  collection_serializer: Type[CollectionSerializer] = attr.ib(
83
90
  default=CollectionSerializer
84
91
  )
92
+ catalog_serializer: Type[CatalogSerializer] = attr.ib(default=CatalogSerializer)
85
93
  post_request_model = attr.ib(default=BaseSearchPostRequest)
86
94
  stac_version: str = attr.ib(default=STAC_VERSION)
87
95
  landing_page_id: str = attr.ib(default="stac-fastapi")
88
96
  title: str = attr.ib(default="stac-fastapi")
89
97
  description: str = attr.ib(default="stac-fastapi")
90
98
 
99
+ def __attrs_post_init__(self):
100
+ """Initialize the queryables cache."""
101
+ self.queryables_cache = QueryablesCache(self.database)
102
+
103
+ def extension_is_enabled(self, extension_name: str) -> bool:
104
+ """Check if an extension is enabled by checking self.extensions.
105
+
106
+ Args:
107
+ extension_name: Name of the extension class to check for.
108
+
109
+ Returns:
110
+ True if the extension is in self.extensions, False otherwise.
111
+ """
112
+ return any(ext.__class__.__name__ == extension_name for ext in self.extensions)
113
+
91
114
  def _landing_page(
92
115
  self,
93
116
  base_url: str,
@@ -151,6 +174,7 @@ class CoreClient(AsyncBaseCoreClient):
151
174
  API landing page, serving as an entry point to the API.
152
175
  """
153
176
  request: Request = kwargs["request"]
177
+
154
178
  base_url = get_base_url(request)
155
179
  landing_page = self._landing_page(
156
180
  base_url=base_url,
@@ -208,6 +232,16 @@ class CoreClient(AsyncBaseCoreClient):
208
232
  ]
209
233
  )
210
234
 
235
+ if self.extension_is_enabled("CatalogsExtension"):
236
+ landing_page["links"].append(
237
+ {
238
+ "rel": "catalogs",
239
+ "type": "application/json",
240
+ "title": "Catalogs",
241
+ "href": urljoin(base_url, "catalogs"),
242
+ }
243
+ )
244
+
211
245
  # Add OpenAPI URL
212
246
  landing_page["links"].append(
213
247
  {
@@ -426,6 +460,8 @@ class CoreClient(AsyncBaseCoreClient):
426
460
  ]
427
461
 
428
462
  if redis_enable:
463
+ from stac_fastapi.core.redis_utils import redis_pagination_links
464
+
429
465
  await redis_pagination_links(
430
466
  current_url=str(request.url),
431
467
  token=token,
@@ -758,7 +794,7 @@ class CoreClient(AsyncBaseCoreClient):
758
794
 
759
795
  body_limit = None
760
796
  try:
761
- if request.method == "POST" and request.body():
797
+ if request.method == "POST" and await request.body():
762
798
  body_data = await request.json()
763
799
  body_limit = body_data.get("limit")
764
800
  except Exception:
@@ -790,9 +826,10 @@ class CoreClient(AsyncBaseCoreClient):
790
826
  search=search, collection_ids=search_request.collections
791
827
  )
792
828
 
829
+ datetime_parsed = format_datetime_range(date_str=search_request.datetime)
793
830
  try:
794
831
  search, datetime_search = self.database.apply_datetime_filter(
795
- search=search, datetime=search_request.datetime
832
+ search=search, datetime=datetime_parsed
796
833
  )
797
834
  except (ValueError, TypeError) as e:
798
835
  # Handle invalid interval formats if return_date fails
@@ -815,6 +852,8 @@ class CoreClient(AsyncBaseCoreClient):
815
852
  )
816
853
 
817
854
  if hasattr(search_request, "query") and getattr(search_request, "query"):
855
+ query_fields = set(getattr(search_request, "query").keys())
856
+ await self.queryables_cache.validate(query_fields)
818
857
  for field_name, expr in getattr(search_request, "query").items():
819
858
  field = "properties__" + field_name
820
859
  for op, value in expr.items():
@@ -833,7 +872,11 @@ class CoreClient(AsyncBaseCoreClient):
833
872
 
834
873
  if cql2_filter is not None:
835
874
  try:
875
+ query_fields = get_properties_from_cql2_filter(cql2_filter)
876
+ await self.queryables_cache.validate(query_fields)
836
877
  search = await self.database.apply_cql2_filter(search, cql2_filter)
878
+ except HTTPException:
879
+ raise
837
880
  except Exception as e:
838
881
  raise HTTPException(
839
882
  status_code=400, detail=f"Error with cql2 filter: {e}"
@@ -903,6 +946,8 @@ class CoreClient(AsyncBaseCoreClient):
903
946
  links.extend(collection_links)
904
947
 
905
948
  if redis_enable:
949
+ from stac_fastapi.core.redis_utils import redis_pagination_links
950
+
906
951
  await redis_pagination_links(
907
952
  current_url=str(request.url),
908
953
  token=token_param,
@@ -32,7 +32,10 @@ def format_datetime_range(date_str: str) -> str:
32
32
  dt_utc = MIN_DATE_NANOS
33
33
  if dt_utc > MAX_DATE_NANOS:
34
34
  dt_utc = MAX_DATE_NANOS
35
- return dt_utc.isoformat(timespec="auto").replace("+00:00", "Z")
35
+ dt_normalized = dt_utc.isoformat(timespec="auto").replace("+00:00", "Z")
36
+ if "." not in dt_normalized:
37
+ dt_normalized = dt_normalized.replace("Z", ".0Z")
38
+ return dt_normalized
36
39
 
37
40
  if not isinstance(date_str, str):
38
41
  return f"{MIN_DATE_NANOS.isoformat(timespec='auto').replace('+00:00','Z')}/{MAX_DATE_NANOS.isoformat(timespec='auto').replace('+00:00','Z')}"
@@ -1,5 +1,6 @@
1
1
  """elasticsearch extensions modifications."""
2
2
 
3
+ from .catalogs import CatalogsExtension
3
4
  from .collections_search import CollectionsSearchEndpointExtension
4
5
  from .query import Operator, QueryableTypes, QueryExtension
5
6
 
@@ -8,4 +9,5 @@ __all__ = [
8
9
  "QueryableTypes",
9
10
  "QueryExtension",
10
11
  "CollectionsSearchEndpointExtension",
12
+ "CatalogsExtension",
11
13
  ]