stac-fastapi-opensearch 6.7.5__tar.gz → 6.8.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stac_fastapi_opensearch
3
- Version: 6.7.5
3
+ Version: 6.8.0
4
4
  Summary: An implementation of STAC API based on the FastAPI framework with Opensearch.
5
5
  Project-URL: Homepage, https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch
6
6
  License: MIT
@@ -16,8 +16,8 @@ Classifier: Programming Language :: Python :: 3.14
16
16
  Requires-Python: >=3.11
17
17
  Requires-Dist: opensearch-py[async]~=2.8.0
18
18
  Requires-Dist: opensearch-py~=2.8.0
19
- Requires-Dist: sfeos-helpers==6.7.5
20
- Requires-Dist: stac-fastapi-core==6.7.5
19
+ Requires-Dist: sfeos-helpers==6.8.0
20
+ Requires-Dist: stac-fastapi-core==6.8.0
21
21
  Requires-Dist: starlette<0.36.0,>=0.35.0
22
22
  Requires-Dist: uvicorn~=0.23.0
23
23
  Provides-Extra: dev
@@ -29,13 +29,13 @@ Requires-Dist: pytest-cov~=4.0.0; extra == 'dev'
29
29
  Requires-Dist: pytest~=8.0; extra == 'dev'
30
30
  Requires-Dist: redis~=6.4.0; extra == 'dev'
31
31
  Requires-Dist: retry~=0.9.2; extra == 'dev'
32
- Requires-Dist: stac-fastapi-core[redis]==6.7.5; extra == 'dev'
32
+ Requires-Dist: stac-fastapi-core[redis]==6.8.0; extra == 'dev'
33
33
  Provides-Extra: docs
34
34
  Requires-Dist: mkdocs-material~=9.0.0; extra == 'docs'
35
35
  Requires-Dist: mkdocs~=1.4.0; extra == 'docs'
36
36
  Requires-Dist: pdocs~=1.2.0; extra == 'docs'
37
37
  Provides-Extra: redis
38
- Requires-Dist: stac-fastapi-core[redis]==6.7.5; extra == 'redis'
38
+ Requires-Dist: stac-fastapi-core[redis]==6.8.0; extra == 'redis'
39
39
  Provides-Extra: server
40
40
  Requires-Dist: uvicorn[standard]~=0.23.0; extra == 'server'
41
41
  Description-Content-Type: text/markdown
@@ -52,7 +52,7 @@ Description-Content-Type: text/markdown
52
52
  [![GitHub forks](https://img.shields.io/github/forks/stac-utils/stac-fastapi-elasticsearch-opensearch.svg?color=blue)](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/network/members)
53
53
  [![PyPI version](https://img.shields.io/pypi/v/stac-fastapi-elasticsearch.svg?color=blue)](https://pypi.org/project/stac-fastapi-elasticsearch/)
54
54
  [![STAC](https://img.shields.io/badge/STAC-1.1.0-blue.svg)](https://github.com/radiantearth/stac-spec/tree/v1.1.0)
55
- [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.0.0-blue.svg)](https://github.com/stac-utils/stac-fastapi)
55
+ [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.1.1-blue.svg)](https://github.com/stac-utils/stac-fastapi)
56
56
 
57
57
  This is the OpenSearch backend for stac-fastapi. For full documentation, please see the [main README](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/blob/main/README.md).
58
58
 
@@ -10,7 +10,7 @@
10
10
  [![GitHub forks](https://img.shields.io/github/forks/stac-utils/stac-fastapi-elasticsearch-opensearch.svg?color=blue)](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/network/members)
11
11
  [![PyPI version](https://img.shields.io/pypi/v/stac-fastapi-elasticsearch.svg?color=blue)](https://pypi.org/project/stac-fastapi-elasticsearch/)
12
12
  [![STAC](https://img.shields.io/badge/STAC-1.1.0-blue.svg)](https://github.com/radiantearth/stac-spec/tree/v1.1.0)
13
- [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.0.0-blue.svg)](https://github.com/stac-utils/stac-fastapi)
13
+ [![stac-fastapi](https://img.shields.io/badge/stac--fastapi-6.1.1-blue.svg)](https://github.com/stac-utils/stac-fastapi)
14
14
 
15
15
  This is the OpenSearch backend for stac-fastapi. For full documentation, please see the [main README](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/blob/main/README.md).
16
16
 
@@ -28,8 +28,8 @@ keywords = [
28
28
  ]
29
29
  dynamic = ["version"]
30
30
  dependencies = [
31
- "stac-fastapi-core==6.7.5",
32
- "sfeos-helpers==6.7.5",
31
+ "stac-fastapi-core==6.8.0",
32
+ "sfeos-helpers==6.8.0",
33
33
  "opensearch-py~=2.8.0",
34
34
  "opensearch-py[async]~=2.8.0",
35
35
  "uvicorn~=0.23.0",
@@ -49,7 +49,7 @@ dev = [
49
49
  "httpx>=0.24.0,<0.28.0",
50
50
  "redis~=6.4.0",
51
51
  "retry~=0.9.2",
52
- "stac-fastapi-core[redis]==6.7.5",
52
+ "stac-fastapi-core[redis]==6.8.0",
53
53
  ]
54
54
  docs = [
55
55
  "mkdocs~=1.4.0",
@@ -57,7 +57,7 @@ docs = [
57
57
  "pdocs~=1.2.0",
58
58
  ]
59
59
  redis = [
60
- "stac-fastapi-core[redis]==6.7.5",
60
+ "stac-fastapi-core[redis]==6.8.0",
61
61
  ]
62
62
  server = [
63
63
  "uvicorn[standard]~=0.23.0",
@@ -23,6 +23,7 @@ from stac_fastapi.core.extensions.aggregation import (
23
23
  EsAggregationExtensionGetRequest,
24
24
  EsAggregationExtensionPostRequest,
25
25
  )
26
+ from stac_fastapi.core.extensions.catalogs import CatalogsExtension
26
27
  from stac_fastapi.core.extensions.collections_search import (
27
28
  CollectionsSearchEndpointExtension,
28
29
  )
@@ -65,11 +66,13 @@ ENABLE_COLLECTIONS_SEARCH = get_bool_env("ENABLE_COLLECTIONS_SEARCH", default=Tr
65
66
  ENABLE_COLLECTIONS_SEARCH_ROUTE = get_bool_env(
66
67
  "ENABLE_COLLECTIONS_SEARCH_ROUTE", default=False
67
68
  )
69
+ ENABLE_CATALOGS_ROUTE = get_bool_env("ENABLE_CATALOGS_ROUTE", default=False)
68
70
  logger.info("TRANSACTIONS_EXTENSIONS is set to %s", TRANSACTIONS_EXTENSIONS)
69
71
  logger.info("ENABLE_COLLECTIONS_SEARCH is set to %s", ENABLE_COLLECTIONS_SEARCH)
70
72
  logger.info(
71
73
  "ENABLE_COLLECTIONS_SEARCH_ROUTE is set to %s", ENABLE_COLLECTIONS_SEARCH_ROUTE
72
74
  )
75
+ logger.info("ENABLE_CATALOGS_ROUTE is set to %s", ENABLE_CATALOGS_ROUTE)
73
76
 
74
77
  settings = OpensearchSettings()
75
78
  session = Session.create_from_settings(settings)
@@ -201,6 +204,22 @@ if ENABLE_COLLECTIONS_SEARCH_ROUTE:
201
204
  extensions.append(collections_search_endpoint_ext)
202
205
 
203
206
 
207
+ if ENABLE_CATALOGS_ROUTE:
208
+ catalogs_extension = CatalogsExtension(
209
+ client=CoreClient(
210
+ database=database_logic,
211
+ session=session,
212
+ post_request_model=collection_search_post_request_model,
213
+ landing_page_id=os.getenv("STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi"),
214
+ ),
215
+ settings=settings,
216
+ conformance_classes=[
217
+ "https://api.stacspec.org/v1.0.0-beta.1/catalogs-endpoint",
218
+ ],
219
+ )
220
+ extensions.append(catalogs_extension)
221
+
222
+
204
223
  database_logic.extensions = [type(ext).__name__ for ext in extensions]
205
224
 
206
225
  post_request_model = create_post_request_model(search_extensions)
@@ -224,7 +243,7 @@ items_get_request_model = create_request_model(
224
243
  app_config = {
225
244
  "title": os.getenv("STAC_FASTAPI_TITLE", "stac-fastapi-opensearch"),
226
245
  "description": os.getenv("STAC_FASTAPI_DESCRIPTION", "stac-fastapi-opensearch"),
227
- "api_version": os.getenv("STAC_FASTAPI_VERSION", "6.0.0"),
246
+ "api_version": os.getenv("STAC_FASTAPI_VERSION", "6.8.0"),
228
247
  "settings": settings,
229
248
  "extensions": extensions,
230
249
  "client": CoreClient(
@@ -56,7 +56,7 @@ def _es_config() -> Dict[str, Any]:
56
56
 
57
57
  # Include timeout setting if set
58
58
  if timeout := os.getenv("ES_TIMEOUT"):
59
- config["timeout"] = timeout
59
+ config["timeout"] = int(timeout)
60
60
 
61
61
  # Explicitly exclude SSL settings when not using SSL
62
62
  if not use_ssl:
@@ -1,7 +1,7 @@
1
1
  """Database logic."""
2
-
3
2
  import asyncio
4
3
  import logging
4
+ import os
5
5
  from base64 import urlsafe_b64decode, urlsafe_b64encode
6
6
  from collections.abc import Iterable
7
7
  from copy import deepcopy
@@ -15,6 +15,7 @@ from opensearchpy.helpers.query import Q
15
15
  from opensearchpy.helpers.search import Search
16
16
  from starlette.requests import Request
17
17
 
18
+ import stac_fastapi.sfeos_helpers.filter as filter_module
18
19
  from stac_fastapi.core.base_database_logic import BaseDatabaseLogic
19
20
  from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
20
21
  from stac_fastapi.core.utilities import MAX_LIMIT, bbox2polygon, get_bool_env
@@ -27,7 +28,6 @@ from stac_fastapi.opensearch.config import (
27
28
  AsyncOpensearchSettings as AsyncSearchSettings,
28
29
  )
29
30
  from stac_fastapi.opensearch.config import OpensearchSettings as SyncSearchSettings
30
- from stac_fastapi.sfeos_helpers import filter as filter_module
31
31
  from stac_fastapi.sfeos_helpers.database import (
32
32
  add_bbox_shape_to_collection,
33
33
  apply_collections_bbox_filter_shared,
@@ -154,6 +154,26 @@ class DatabaseLogic(BaseDatabaseLogic):
154
154
 
155
155
  aggregation_mapping: Dict[str, Dict[str, Any]] = AGGREGATION_MAPPING
156
156
 
157
+ # constants for field names
158
+ # they are used in multiple methods
159
+ # and could be overwritten in subclasses used with alternate opensearch mappings.
160
+ PROPERTIES_DATETIME_FIELD = os.getenv(
161
+ "STAC_FIELD_PROP_DATETIME", "properties.datetime"
162
+ )
163
+ PROPERTIES_START_DATETIME_FIELD = os.getenv(
164
+ "STAC_FIELD_PROP_START_DATETIME", "properties.start_datetime"
165
+ )
166
+ PROPERTIES_END_DATETIME_FIELD = os.getenv(
167
+ "STAC_FIELD_PROP_END_DATETIME", "properties.end_datetime"
168
+ )
169
+ COLLECTION_FIELD = os.getenv("STAC_FIELD_COLLECTION", "collection")
170
+ GEOMETRY_FIELD = os.getenv("STAC_FIELD_GEOMETRY", "geometry")
171
+
172
+ @staticmethod
173
+ def __nested_field__(field: str):
174
+ """Convert opensearch field to nested field format."""
175
+ return field.replace(".", "__")
176
+
157
177
  """CORE LOGIC"""
158
178
 
159
179
  async def get_all_collections(
@@ -436,7 +456,10 @@ class DatabaseLogic(BaseDatabaseLogic):
436
456
  @staticmethod
437
457
  def apply_collections_filter(search: Search, collection_ids: List[str]):
438
458
  """Database logic to search a list of STAC collection ids."""
439
- return search.filter("terms", collection=collection_ids)
459
+ collection_nested_field = DatabaseLogic.__nested_field__(
460
+ DatabaseLogic.COLLECTION_FIELD
461
+ )
462
+ return search.filter("terms", **{collection_nested_field: collection_ids})
440
463
 
441
464
  @staticmethod
442
465
  def apply_free_text_filter(search: Search, free_text_queries: Optional[List[str]]):
@@ -479,6 +502,16 @@ class DatabaseLogic(BaseDatabaseLogic):
479
502
  # False: Always search only by start/end datetime
480
503
  USE_DATETIME = get_bool_env("USE_DATETIME", default=True)
481
504
 
505
+ nested_datetime_field = DatabaseLogic.__nested_field__(
506
+ DatabaseLogic.PROPERTIES_DATETIME_FIELD
507
+ )
508
+ nested_start_datetime_field = DatabaseLogic.__nested_field__(
509
+ DatabaseLogic.PROPERTIES_START_DATETIME_FIELD
510
+ )
511
+ nested_end_datetime_field = DatabaseLogic.__nested_field__(
512
+ DatabaseLogic.PROPERTIES_END_DATETIME_FIELD
513
+ )
514
+
482
515
  if USE_DATETIME:
483
516
  if "eq" in datetime_search:
484
517
  # For exact matches, include:
@@ -488,28 +521,42 @@ class DatabaseLogic(BaseDatabaseLogic):
488
521
  Q(
489
522
  "bool",
490
523
  filter=[
491
- Q("exists", field="properties.datetime"),
524
+ Q("exists", field=DatabaseLogic.PROPERTIES_DATETIME_FIELD),
492
525
  Q(
493
526
  "term",
494
- **{"properties__datetime": datetime_search["eq"]},
527
+ **{nested_datetime_field: datetime_search["eq"]},
495
528
  ),
496
529
  ],
497
530
  ),
498
531
  Q(
499
532
  "bool",
500
- must_not=[Q("exists", field="properties.datetime")],
533
+ must_not=[
534
+ Q("exists", field=DatabaseLogic.PROPERTIES_DATETIME_FIELD)
535
+ ],
501
536
  filter=[
502
- Q("exists", field="properties.start_datetime"),
503
- Q("exists", field="properties.end_datetime"),
537
+ Q(
538
+ "exists",
539
+ field=DatabaseLogic.PROPERTIES_START_DATETIME_FIELD,
540
+ ),
541
+ Q(
542
+ "exists",
543
+ field=DatabaseLogic.PROPERTIES_END_DATETIME_FIELD,
544
+ ),
504
545
  Q(
505
546
  "range",
506
- properties__start_datetime={
507
- "lte": datetime_search["eq"]
547
+ **{
548
+ nested_start_datetime_field: {
549
+ "lte": datetime_search["eq"]
550
+ }
508
551
  },
509
552
  ),
510
553
  Q(
511
554
  "range",
512
- properties__end_datetime={"gte": datetime_search["eq"]},
555
+ **{
556
+ nested_end_datetime_field: {
557
+ "gte": datetime_search["eq"]
558
+ }
559
+ },
513
560
  ),
514
561
  ],
515
562
  ),
@@ -522,32 +569,46 @@ class DatabaseLogic(BaseDatabaseLogic):
522
569
  Q(
523
570
  "bool",
524
571
  filter=[
525
- Q("exists", field="properties.datetime"),
572
+ Q("exists", field=DatabaseLogic.PROPERTIES_DATETIME_FIELD),
526
573
  Q(
527
574
  "range",
528
- properties__datetime={
529
- "gte": datetime_search["gte"],
530
- "lte": datetime_search["lte"],
575
+ **{
576
+ nested_datetime_field: {
577
+ "gte": datetime_search["gte"],
578
+ "lte": datetime_search["lte"],
579
+ }
531
580
  },
532
581
  ),
533
582
  ],
534
583
  ),
535
584
  Q(
536
585
  "bool",
537
- must_not=[Q("exists", field="properties.datetime")],
586
+ must_not=[
587
+ Q("exists", field=DatabaseLogic.PROPERTIES_DATETIME_FIELD)
588
+ ],
538
589
  filter=[
539
- Q("exists", field="properties.start_datetime"),
540
- Q("exists", field="properties.end_datetime"),
590
+ Q(
591
+ "exists",
592
+ field=DatabaseLogic.PROPERTIES_START_DATETIME_FIELD,
593
+ ),
594
+ Q(
595
+ "exists",
596
+ field=DatabaseLogic.PROPERTIES_END_DATETIME_FIELD,
597
+ ),
541
598
  Q(
542
599
  "range",
543
- properties__start_datetime={
544
- "lte": datetime_search["lte"]
600
+ **{
601
+ nested_start_datetime_field: {
602
+ "lte": datetime_search["lte"]
603
+ }
545
604
  },
546
605
  ),
547
606
  Q(
548
607
  "range",
549
- properties__end_datetime={
550
- "gte": datetime_search["gte"]
608
+ **{
609
+ nested_end_datetime_field: {
610
+ "gte": datetime_search["gte"]
611
+ }
551
612
  },
552
613
  ),
553
614
  ],
@@ -563,15 +624,26 @@ class DatabaseLogic(BaseDatabaseLogic):
563
624
  filter_query = Q(
564
625
  "bool",
565
626
  filter=[
566
- Q("exists", field="properties.start_datetime"),
567
- Q("exists", field="properties.end_datetime"),
627
+ Q(
628
+ "exists",
629
+ field=DatabaseLogic.PROPERTIES_START_DATETIME_FIELD,
630
+ ),
631
+ Q("exists", field=DatabaseLogic.PROPERTIES_END_DATETIME_FIELD),
568
632
  Q(
569
633
  "range",
570
- properties__start_datetime={"lte": datetime_search["eq"]},
634
+ **{
635
+ nested_start_datetime_field: {
636
+ "lte": datetime_search["eq"]
637
+ }
638
+ },
571
639
  ),
572
640
  Q(
573
641
  "range",
574
- properties__end_datetime={"gte": datetime_search["eq"]},
642
+ **{
643
+ nested_end_datetime_field: {
644
+ "gte": datetime_search["eq"]
645
+ }
646
+ },
575
647
  ),
576
648
  ],
577
649
  )
@@ -579,15 +651,26 @@ class DatabaseLogic(BaseDatabaseLogic):
579
651
  filter_query = Q(
580
652
  "bool",
581
653
  filter=[
582
- Q("exists", field="properties.start_datetime"),
583
- Q("exists", field="properties.end_datetime"),
654
+ Q(
655
+ "exists",
656
+ field=DatabaseLogic.PROPERTIES_START_DATETIME_FIELD,
657
+ ),
658
+ Q("exists", field=DatabaseLogic.PROPERTIES_END_DATETIME_FIELD),
584
659
  Q(
585
660
  "range",
586
- properties__start_datetime={"lte": datetime_search["lte"]},
661
+ **{
662
+ nested_start_datetime_field: {
663
+ "lte": datetime_search["lte"]
664
+ }
665
+ },
587
666
  ),
588
667
  Q(
589
668
  "range",
590
- properties__end_datetime={"gte": datetime_search["gte"]},
669
+ **{
670
+ nested_end_datetime_field: {
671
+ "gte": datetime_search["gte"]
672
+ }
673
+ },
591
674
  ),
592
675
  ],
593
676
  )
@@ -612,7 +695,7 @@ class DatabaseLogic(BaseDatabaseLogic):
612
695
  Q(
613
696
  {
614
697
  "geo_shape": {
615
- "geometry": {
698
+ DatabaseLogic.GEOMETRY_FIELD: {
616
699
  "shape": {
617
700
  "type": "polygon",
618
701
  "coordinates": bbox2polygon(*bbox),
@@ -1723,3 +1806,132 @@ class DatabaseLogic(BaseDatabaseLogic):
1723
1806
  body={"query": {"match_all": {}}},
1724
1807
  wait_for_completion=True,
1725
1808
  )
1809
+
1810
+ """CATALOGS LOGIC"""
1811
+
1812
+ async def get_all_catalogs(
1813
+ self,
1814
+ token: Optional[str],
1815
+ limit: int,
1816
+ request: Any = None,
1817
+ sort: Optional[List[Dict[str, Any]]] = None,
1818
+ ) -> Tuple[List[Dict[str, Any]], Optional[str], Optional[int]]:
1819
+ """Retrieve a list of catalogs from OpenSearch, supporting pagination.
1820
+
1821
+ Args:
1822
+ token (Optional[str]): The pagination token.
1823
+ limit (int): The number of results to return.
1824
+ request (Any, optional): The FastAPI request object. Defaults to None.
1825
+ sort (Optional[List[Dict[str, Any]]], optional): Optional sort parameter. Defaults to None.
1826
+
1827
+ Returns:
1828
+ A tuple of (catalogs, next pagination token if any, optional count).
1829
+ """
1830
+ # Define sortable fields for catalogs
1831
+ sortable_fields = ["id"]
1832
+
1833
+ # Format the sort parameter
1834
+ formatted_sort = []
1835
+ if sort:
1836
+ for item in sort:
1837
+ field = item.get("field")
1838
+ direction = item.get("direction", "asc")
1839
+ if field and field in sortable_fields:
1840
+ formatted_sort.append({field: {"order": direction}})
1841
+
1842
+ if not formatted_sort:
1843
+ formatted_sort = [{"id": {"order": "asc"}}]
1844
+
1845
+ body = {
1846
+ "sort": formatted_sort,
1847
+ "size": limit,
1848
+ "query": {"term": {"type": "Catalog"}},
1849
+ }
1850
+
1851
+ # Handle search_after token
1852
+ search_after = None
1853
+ if token:
1854
+ try:
1855
+ search_after = token.split("|")
1856
+ if len(search_after) != len(formatted_sort):
1857
+ search_after = None
1858
+ except Exception:
1859
+ search_after = None
1860
+
1861
+ if search_after is not None:
1862
+ body["search_after"] = search_after
1863
+
1864
+ # Search for catalogs in collections index
1865
+ response = await self.client.search(
1866
+ index=COLLECTIONS_INDEX,
1867
+ body=body,
1868
+ )
1869
+
1870
+ hits = response["hits"]["hits"]
1871
+ catalogs = [hit["_source"] for hit in hits]
1872
+
1873
+ next_token = None
1874
+ if len(hits) == limit:
1875
+ next_token_values = hits[-1].get("sort")
1876
+ if next_token_values:
1877
+ next_token = "|".join(str(val) for val in next_token_values)
1878
+
1879
+ # Get the total count
1880
+ matched = (
1881
+ response["hits"]["total"]["value"]
1882
+ if response["hits"]["total"]["relation"] == "eq"
1883
+ else None
1884
+ )
1885
+
1886
+ return catalogs, next_token, matched
1887
+
1888
+ async def create_catalog(self, catalog: Dict, refresh: bool = False) -> None:
1889
+ """Create a catalog in OpenSearch.
1890
+
1891
+ Args:
1892
+ catalog (Dict): The catalog document to create.
1893
+ refresh (bool): Whether to refresh the index after creation.
1894
+ """
1895
+ await self.client.index(
1896
+ index=COLLECTIONS_INDEX,
1897
+ id=catalog.get("id"),
1898
+ body=catalog,
1899
+ refresh=refresh,
1900
+ )
1901
+
1902
+ async def find_catalog(self, catalog_id: str) -> Dict:
1903
+ """Find a catalog in OpenSearch by ID.
1904
+
1905
+ Args:
1906
+ catalog_id (str): The ID of the catalog to find.
1907
+
1908
+ Returns:
1909
+ Dict: The catalog document.
1910
+
1911
+ Raises:
1912
+ NotFoundError: If the catalog is not found.
1913
+ """
1914
+ try:
1915
+ response = await self.client.get(
1916
+ index=COLLECTIONS_INDEX,
1917
+ id=catalog_id,
1918
+ )
1919
+ # Verify it's a catalog
1920
+ if response["_source"].get("type") != "Catalog":
1921
+ raise NotFoundError(f"Catalog {catalog_id} not found")
1922
+ return response["_source"]
1923
+ except exceptions.NotFoundError:
1924
+ raise NotFoundError(f"Catalog {catalog_id} not found")
1925
+
1926
+ async def delete_catalog(self, catalog_id: str, refresh: bool = False) -> None:
1927
+ """Delete a catalog from OpenSearch.
1928
+
1929
+ Args:
1930
+ catalog_id (str): The ID of the catalog to delete.
1931
+ refresh (bool): Whether to refresh the index after deletion.
1932
+ """
1933
+ await self.client.delete(
1934
+ index=COLLECTIONS_INDEX,
1935
+ id=catalog_id,
1936
+ refresh=refresh,
1937
+ )
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "6.7.5"
2
+ __version__ = "6.8.0"