stac-fastapi.core 2.0.0__tar.gz → 2.3.0__tar.gz

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.
Files changed (26) hide show
  1. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/PKG-INFO +4 -4
  2. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/setup.py +3 -3
  3. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/core.py +77 -99
  4. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/models/links.py +2 -2
  5. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/serializers.py +19 -5
  6. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/types/core.py +1 -1
  7. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/version.py +1 -1
  8. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi.core.egg-info/PKG-INFO +4 -4
  9. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi.core.egg-info/requires.txt +3 -3
  10. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/README.md +0 -0
  11. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/setup.cfg +0 -0
  12. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/__init__.py +0 -0
  13. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/base_database_logic.py +0 -0
  14. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/base_settings.py +0 -0
  15. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/datetime_utils.py +0 -0
  16. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/extensions/__init__.py +0 -0
  17. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/extensions/filter.py +0 -0
  18. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/extensions/query.py +0 -0
  19. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/models/__init__.py +0 -0
  20. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/models/search.py +0 -0
  21. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/session.py +0 -0
  22. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi/core/utilities.py +0 -0
  23. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi.core.egg-info/SOURCES.txt +0 -0
  24. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi.core.egg-info/dependency_links.txt +0 -0
  25. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi.core.egg-info/not-zip-safe +0 -0
  26. {stac-fastapi.core-2.0.0 → stac_fastapi_core-2.3.0}/stac_fastapi.core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi.core
3
- Version: 2.0.0
3
+ Version: 2.3.0
4
4
  Summary: Core library for the Elasticsearch and Opensearch stac-fastapi backends.
5
5
  Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -18,9 +18,9 @@ Requires-Dist: fastapi
18
18
  Requires-Dist: attrs
19
19
  Requires-Dist: pydantic[dotenv]<2
20
20
  Requires-Dist: stac_pydantic==2.0.*
21
- Requires-Dist: stac-fastapi.types==2.4.9
22
- Requires-Dist: stac-fastapi.api==2.4.9
23
- Requires-Dist: stac-fastapi.extensions==2.4.9
21
+ Requires-Dist: stac-fastapi.types==2.5.3
22
+ Requires-Dist: stac-fastapi.api==2.5.3
23
+ Requires-Dist: stac-fastapi.extensions==2.5.3
24
24
  Requires-Dist: pystac[validation]
25
25
  Requires-Dist: orjson
26
26
  Requires-Dist: overrides
@@ -10,9 +10,9 @@ install_requires = [
10
10
  "attrs",
11
11
  "pydantic[dotenv]<2",
12
12
  "stac_pydantic==2.0.*",
13
- "stac-fastapi.types==2.4.9",
14
- "stac-fastapi.api==2.4.9",
15
- "stac-fastapi.extensions==2.4.9",
13
+ "stac-fastapi.types==2.5.3",
14
+ "stac-fastapi.api==2.5.3",
15
+ "stac-fastapi.extensions==2.5.3",
16
16
  "pystac[validation]",
17
17
  "orjson",
18
18
  "overrides",
@@ -1,7 +1,6 @@
1
- """Item crud client."""
1
+ """Core client."""
2
2
  import logging
3
3
  import re
4
- from base64 import urlsafe_b64encode
5
4
  from datetime import datetime as datetime_type
6
5
  from datetime import timezone
7
6
  from typing import Any, Dict, List, Optional, Set, Type, Union
@@ -16,7 +15,7 @@ from pydantic import ValidationError
16
15
  from pygeofilter.backends.cql2_json import to_cql2
17
16
  from pygeofilter.parsers.cql2_text import parse as parse_cql2_text
18
17
  from stac_pydantic.links import Relations
19
- from stac_pydantic.shared import MimeTypes
18
+ from stac_pydantic.shared import BBox, MimeTypes
20
19
  from stac_pydantic.version import STAC_VERSION
21
20
 
22
21
  from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
@@ -38,8 +37,8 @@ from stac_fastapi.types import stac as stac_types
38
37
  from stac_fastapi.types.config import Settings
39
38
  from stac_fastapi.types.conformance import BASE_CONFORMANCE_CLASSES
40
39
  from stac_fastapi.types.extension import ApiExtension
41
- from stac_fastapi.types.links import CollectionLinks
42
40
  from stac_fastapi.types.requests import get_base_url
41
+ from stac_fastapi.types.rfc3339 import DateTimeType
43
42
  from stac_fastapi.types.search import BaseSearchPostRequest
44
43
  from stac_fastapi.types.stac import Collection, Collections, Item, ItemCollection
45
44
 
@@ -193,49 +192,24 @@ class CoreClient(AsyncBaseCoreClient):
193
192
  async def all_collections(self, **kwargs) -> Collections:
194
193
  """Read all collections from the database.
195
194
 
196
- Returns:
197
- Collections: A `Collections` object containing all the collections in the database and
198
- links to various resources.
195
+ Args:
196
+ **kwargs: Keyword arguments from the request.
199
197
 
200
- Raises:
201
- Exception: If any error occurs while reading the collections from the database.
198
+ Returns:
199
+ A Collections object containing all the collections in the database and links to various resources.
202
200
  """
203
- request: Request = kwargs["request"]
204
- base_url = str(kwargs["request"].base_url)
201
+ request = kwargs["request"]
202
+ base_url = str(request.base_url)
203
+ limit = int(request.query_params.get("limit", 10))
204
+ token = request.query_params.get("token")
205
205
 
206
- limit = (
207
- int(request.query_params["limit"])
208
- if "limit" in request.query_params
209
- else 10
210
- )
211
- token = (
212
- request.query_params["token"] if "token" in request.query_params else None
206
+ collections, next_token = await self.database.get_all_collections(
207
+ token=token, limit=limit, base_url=base_url
213
208
  )
214
209
 
215
- hits = await self.database.get_all_collections(limit=limit, token=token)
216
-
217
- next_search_after = None
218
- next_link = None
219
- if len(hits) == limit:
220
- last_hit = hits[-1]
221
- next_search_after = last_hit["sort"]
222
- next_token = urlsafe_b64encode(
223
- ",".join(map(str, next_search_after)).encode()
224
- ).decode()
225
- paging_links = PagingLinks(next=next_token, request=request)
226
- next_link = paging_links.link_next()
227
-
228
210
  links = [
229
- {
230
- "rel": Relations.root.value,
231
- "type": MimeTypes.json,
232
- "href": base_url,
233
- },
234
- {
235
- "rel": Relations.parent.value,
236
- "type": MimeTypes.json,
237
- "href": base_url,
238
- },
211
+ {"rel": Relations.root.value, "type": MimeTypes.json, "href": base_url},
212
+ {"rel": Relations.parent.value, "type": MimeTypes.json, "href": base_url},
239
213
  {
240
214
  "rel": Relations.self.value,
241
215
  "type": MimeTypes.json,
@@ -243,16 +217,11 @@ class CoreClient(AsyncBaseCoreClient):
243
217
  },
244
218
  ]
245
219
 
246
- if next_link:
220
+ if next_token:
221
+ next_link = PagingLinks(next=next_token, request=request).link_next()
247
222
  links.append(next_link)
248
223
 
249
- return Collections(
250
- collections=[
251
- self.collection_serializer.db_to_stac(c["_source"], base_url=base_url)
252
- for c in hits
253
- ],
254
- links=links,
255
- )
224
+ return Collections(collections=collections, links=links)
256
225
 
257
226
  async def get_collection(self, collection_id: str, **kwargs) -> Collection:
258
227
  """Get a collection from the database by its id.
@@ -269,13 +238,15 @@ class CoreClient(AsyncBaseCoreClient):
269
238
  """
270
239
  base_url = str(kwargs["request"].base_url)
271
240
  collection = await self.database.find_collection(collection_id=collection_id)
272
- return self.collection_serializer.db_to_stac(collection, base_url)
241
+ return self.collection_serializer.db_to_stac(
242
+ collection=collection, base_url=base_url
243
+ )
273
244
 
274
245
  async def item_collection(
275
246
  self,
276
247
  collection_id: str,
277
- bbox: Optional[List[NumType]] = None,
278
- datetime: Union[str, datetime_type, None] = None,
248
+ bbox: Optional[BBox] = None,
249
+ datetime: Optional[DateTimeType] = None,
279
250
  limit: int = 10,
280
251
  token: str = None,
281
252
  **kwargs,
@@ -284,8 +255,8 @@ class CoreClient(AsyncBaseCoreClient):
284
255
 
285
256
  Args:
286
257
  collection_id (str): The identifier of the collection to read items from.
287
- bbox (Optional[List[NumType]]): The bounding box to filter items by.
288
- datetime (Union[str, datetime_type, None]): The datetime range to filter items by.
258
+ bbox (Optional[BBox]): The bounding box to filter items by.
259
+ datetime (Optional[DateTimeType]): The datetime range to filter items by.
289
260
  limit (int): The maximum number of items to return. The default value is 10.
290
261
  token (str): A token used for pagination.
291
262
  request (Request): The incoming request.
@@ -379,53 +350,64 @@ class CoreClient(AsyncBaseCoreClient):
379
350
  return self.item_serializer.db_to_stac(item, base_url)
380
351
 
381
352
  @staticmethod
382
- def _return_date(interval_str):
353
+ def _return_date(
354
+ interval: Optional[Union[DateTimeType, str]]
355
+ ) -> Dict[str, Optional[str]]:
383
356
  """
384
- Convert a date interval string into a dictionary for filtering search results.
357
+ Convert a date interval.
385
358
 
386
- The date interval string should be formatted as either a single date or a range of dates separated
387
- by "/". The date format should be ISO-8601 (YYYY-MM-DDTHH:MM:SSZ). If the interval string is a
388
- single date, it will be converted to a dictionary with a single "eq" key whose value is the date in
389
- the ISO-8601 format. If the interval string is a range of dates, it will be converted to a
390
- dictionary with "gte" (greater than or equal to) and "lte" (less than or equal to) keys. If the
391
- interval string is a range of dates with ".." instead of "/", the start and end dates will be
392
- assigned default values to encompass the entire possible date range.
359
+ (which may be a datetime, a tuple of one or two datetimes a string
360
+ representing a datetime or range, or None) into a dictionary for filtering
361
+ search results with Elasticsearch.
362
+
363
+ This function ensures the output dictionary contains 'gte' and 'lte' keys,
364
+ even if they are set to None, to prevent KeyError in the consuming logic.
393
365
 
394
366
  Args:
395
- interval_str (str): The date interval string to be converted.
367
+ interval (Optional[Union[DateTimeType, str]]): The date interval, which might be a single datetime,
368
+ a tuple with one or two datetimes, a string, or None.
396
369
 
397
370
  Returns:
398
- dict: A dictionary representing the date interval for use in filtering search results.
371
+ dict: A dictionary representing the date interval for use in filtering search results,
372
+ always containing 'gte' and 'lte' keys.
399
373
  """
400
- intervals = interval_str.split("/")
401
- if len(intervals) == 1:
402
- datetime = f"{intervals[0][0:19]}Z"
403
- return {"eq": datetime}
404
- else:
405
- start_date = intervals[0]
406
- end_date = intervals[1]
407
- if ".." not in intervals:
408
- start_date = f"{start_date[0:19]}Z"
409
- end_date = f"{end_date[0:19]}Z"
410
- elif start_date != "..":
411
- start_date = f"{start_date[0:19]}Z"
412
- end_date = "2200-12-01T12:31:12Z"
413
- elif end_date != "..":
414
- start_date = "1900-10-01T00:00:00Z"
415
- end_date = f"{end_date[0:19]}Z"
416
- else:
417
- start_date = "1900-10-01T00:00:00Z"
418
- end_date = "2200-12-01T12:31:12Z"
374
+ result: Dict[str, Optional[str]] = {"gte": None, "lte": None}
375
+
376
+ if interval is None:
377
+ return result
419
378
 
420
- return {"lte": end_date, "gte": start_date}
379
+ if isinstance(interval, str):
380
+ if "/" in interval:
381
+ parts = interval.split("/")
382
+ result["gte"] = parts[0] if parts[0] != ".." else None
383
+ result["lte"] = (
384
+ parts[1] if len(parts) > 1 and parts[1] != ".." else None
385
+ )
386
+ else:
387
+ converted_time = interval if interval != ".." else None
388
+ result["gte"] = result["lte"] = converted_time
389
+ return result
390
+
391
+ if isinstance(interval, datetime_type):
392
+ datetime_iso = interval.isoformat()
393
+ result["gte"] = result["lte"] = datetime_iso
394
+ elif isinstance(interval, tuple):
395
+ start, end = interval
396
+ # Ensure datetimes are converted to UTC and formatted with 'Z'
397
+ if start:
398
+ result["gte"] = start.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
399
+ if end:
400
+ result["lte"] = end.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
401
+
402
+ return result
421
403
 
422
404
  async def get_search(
423
405
  self,
424
406
  request: Request,
425
407
  collections: Optional[List[str]] = None,
426
408
  ids: Optional[List[str]] = None,
427
- bbox: Optional[List[NumType]] = None,
428
- datetime: Optional[Union[str, datetime_type]] = None,
409
+ bbox: Optional[BBox] = None,
410
+ datetime: Optional[DateTimeType] = None,
429
411
  limit: Optional[int] = 10,
430
412
  query: Optional[str] = None,
431
413
  token: Optional[str] = None,
@@ -441,8 +423,8 @@ class CoreClient(AsyncBaseCoreClient):
441
423
  Args:
442
424
  collections (Optional[List[str]]): List of collection IDs to search in.
443
425
  ids (Optional[List[str]]): List of item IDs to search for.
444
- bbox (Optional[List[NumType]]): Bounding box to search in.
445
- datetime (Optional[Union[str, datetime_type]]): Filter items based on the datetime field.
426
+ bbox (Optional[BBox]): Bounding box to search in.
427
+ datetime (Optional[DateTimeType]): Filter items based on the datetime field.
446
428
  limit (Optional[int]): Maximum number of results to return.
447
429
  query (Optional[str]): Query string to filter the results.
448
430
  token (Optional[str]): Access token to use when searching the catalog.
@@ -489,7 +471,6 @@ class CoreClient(AsyncBaseCoreClient):
489
471
  "direction": "desc" if sort[0] == "-" else "asc",
490
472
  }
491
473
  )
492
- print(sort_param)
493
474
  base_args["sortby"] = sort_param
494
475
 
495
476
  if filter:
@@ -760,10 +741,9 @@ class TransactionsClient(AsyncBaseTransactionsClient):
760
741
  ConflictError: If the collection already exists.
761
742
  """
762
743
  base_url = str(kwargs["request"].base_url)
763
- collection_links = CollectionLinks(
764
- collection_id=collection["id"], base_url=base_url
765
- ).create_links()
766
- collection["links"] = collection_links
744
+ collection = self.database.collection_serializer.stac_to_db(
745
+ collection, base_url
746
+ )
767
747
  await self.database.create_collection(collection=collection)
768
748
 
769
749
  return CollectionSerializer.db_to_stac(collection, base_url)
@@ -796,11 +776,9 @@ class TransactionsClient(AsyncBaseTransactionsClient):
796
776
  "collection_id", collection["id"]
797
777
  )
798
778
 
799
- collection_links = CollectionLinks(
800
- collection_id=collection["id"], base_url=base_url
801
- ).create_links()
802
- collection["links"] = collection_links
803
-
779
+ collection = self.database.collection_serializer.stac_to_db(
780
+ collection, base_url
781
+ )
804
782
  await self.database.update_collection(
805
783
  collection_id=collection_id, collection=collection
806
784
  )
@@ -1,7 +1,7 @@
1
1
  """link helpers."""
2
2
 
3
3
  from typing import Any, Dict, List, Optional
4
- from urllib.parse import ParseResult, parse_qs, unquote, urlencode, urljoin, urlparse
4
+ from urllib.parse import ParseResult, parse_qs, urlencode, urljoin, urlparse
5
5
 
6
6
  import attr
7
7
  from stac_pydantic.links import Relations
@@ -20,7 +20,7 @@ def merge_params(url: str, newparams: Dict) -> str:
20
20
  u = urlparse(url)
21
21
  params = parse_qs(u.query)
22
22
  params.update(newparams)
23
- param_string = unquote(urlencode(params, True))
23
+ param_string = urlencode(params, True)
24
24
 
25
25
  href = ParseResult(
26
26
  scheme=u.scheme,
@@ -60,11 +60,7 @@ class ItemSerializer(Serializer):
60
60
  Returns:
61
61
  stac_types.Item: The database-ready STAC item object.
62
62
  """
63
- item_links = ItemLinks(
64
- collection_id=stac_data["collection"],
65
- item_id=stac_data["id"],
66
- base_url=base_url,
67
- ).create_links()
63
+ item_links = resolve_links(stac_data.get("links", []), base_url)
68
64
  stac_data["links"] = item_links
69
65
 
70
66
  now = now_to_rfc3339_str()
@@ -111,6 +107,24 @@ class ItemSerializer(Serializer):
111
107
  class CollectionSerializer(Serializer):
112
108
  """Serialization methods for STAC collections."""
113
109
 
110
+ @classmethod
111
+ def stac_to_db(
112
+ cls, collection: stac_types.Collection, base_url: str
113
+ ) -> stac_types.Collection:
114
+ """
115
+ Transform STAC Collection to database-ready STAC collection.
116
+
117
+ Args:
118
+ stac_data: the STAC Collection object to be transformed
119
+ base_url: the base URL for the STAC API
120
+
121
+ Returns:
122
+ stac_types.Collection: The database-ready STAC Collection object.
123
+ """
124
+ collection = deepcopy(collection)
125
+ collection["links"] = resolve_links(collection.get("links", []), base_url)
126
+ return collection
127
+
114
128
  @classmethod
115
129
  def db_to_stac(cls, collection: dict, base_url: str) -> stac_types.Collection:
116
130
  """Transform database model to STAC collection.
@@ -1,4 +1,4 @@
1
- """Base clients. Takef from stac-fastapi.types.core v2.4.9."""
1
+ """Base clients. Taken from stac-fastapi.types.core v2.4.9."""
2
2
  import abc
3
3
  from datetime import datetime
4
4
  from typing import Any, Dict, List, Optional, Union
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "2.0.0"
2
+ __version__ = "2.3.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi.core
3
- Version: 2.0.0
3
+ Version: 2.3.0
4
4
  Summary: Core library for the Elasticsearch and Opensearch stac-fastapi backends.
5
5
  Home-page: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -18,9 +18,9 @@ Requires-Dist: fastapi
18
18
  Requires-Dist: attrs
19
19
  Requires-Dist: pydantic[dotenv]<2
20
20
  Requires-Dist: stac_pydantic==2.0.*
21
- Requires-Dist: stac-fastapi.types==2.4.9
22
- Requires-Dist: stac-fastapi.api==2.4.9
23
- Requires-Dist: stac-fastapi.extensions==2.4.9
21
+ Requires-Dist: stac-fastapi.types==2.5.3
22
+ Requires-Dist: stac-fastapi.api==2.5.3
23
+ Requires-Dist: stac-fastapi.extensions==2.5.3
24
24
  Requires-Dist: pystac[validation]
25
25
  Requires-Dist: orjson
26
26
  Requires-Dist: overrides
@@ -2,9 +2,9 @@ fastapi
2
2
  attrs
3
3
  pydantic[dotenv]<2
4
4
  stac_pydantic==2.0.*
5
- stac-fastapi.types==2.4.9
6
- stac-fastapi.api==2.4.9
7
- stac-fastapi.extensions==2.4.9
5
+ stac-fastapi.types==2.5.3
6
+ stac-fastapi.api==2.5.3
7
+ stac-fastapi.extensions==2.5.3
8
8
  pystac[validation]
9
9
  orjson
10
10
  overrides