stac-fastapi-core 4.0.0a2__py3-none-any.whl → 4.1.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.
stac_fastapi/core/core.py CHANGED
@@ -676,46 +676,65 @@ class TransactionsClient(AsyncBaseTransactionsClient):
676
676
  @overrides
677
677
  async def create_item(
678
678
  self, collection_id: str, item: Union[Item, ItemCollection], **kwargs
679
- ) -> Optional[stac_types.Item]:
680
- """Create an item in the collection.
679
+ ) -> Union[stac_types.Item, str]:
680
+ """
681
+ Create an item or a feature collection of items in the specified collection.
681
682
 
682
683
  Args:
683
- collection_id (str): The id of the collection to add the item to.
684
- item (stac_types.Item): The item to be added to the collection.
685
- kwargs: Additional keyword arguments.
684
+ collection_id (str): The ID of the collection to add the item(s) to.
685
+ item (Union[Item, ItemCollection]): A single item or a collection of items to be added.
686
+ **kwargs: Additional keyword arguments, such as `request` and `refresh`.
686
687
 
687
688
  Returns:
688
- stac_types.Item: The created item.
689
+ Union[stac_types.Item, str]: The created item if a single item is added, or a summary string
690
+ indicating the number of items successfully added and errors if a collection of items is added.
689
691
 
690
692
  Raises:
691
- NotFound: If the specified collection is not found in the database.
692
- ConflictError: If the item in the specified collection already exists.
693
-
693
+ NotFoundError: If the specified collection is not found in the database.
694
+ ConflictError: If an item with the same ID already exists in the collection.
694
695
  """
695
- item = item.model_dump(mode="json")
696
- base_url = str(kwargs["request"].base_url)
696
+ request = kwargs.get("request")
697
+ base_url = str(request.base_url)
697
698
 
698
- # If a feature collection is posted
699
- if item["type"] == "FeatureCollection":
699
+ # Convert Pydantic model to dict for uniform processing
700
+ item_dict = item.model_dump(mode="json")
701
+
702
+ # Handle FeatureCollection (bulk insert)
703
+ if item_dict["type"] == "FeatureCollection":
700
704
  bulk_client = BulkTransactionsClient(
701
705
  database=self.database, settings=self.settings
702
706
  )
707
+ features = item_dict["features"]
703
708
  processed_items = [
704
709
  bulk_client.preprocess_item(
705
- item, base_url, BulkTransactionMethod.INSERT
710
+ feature, base_url, BulkTransactionMethod.INSERT
706
711
  )
707
- for item in item["features"]
712
+ for feature in features
708
713
  ]
709
-
710
- await self.database.bulk_async(
711
- collection_id, processed_items, refresh=kwargs.get("refresh", False)
714
+ attempted = len(processed_items)
715
+ success, errors = await self.database.bulk_async(
716
+ collection_id,
717
+ processed_items,
718
+ refresh=kwargs.get("refresh", False),
712
719
  )
720
+ if errors:
721
+ logger.error(
722
+ f"Bulk async operation encountered errors for collection {collection_id}: {errors} (attempted {attempted})"
723
+ )
724
+ else:
725
+ logger.info(
726
+ f"Bulk async operation succeeded with {success} actions for collection {collection_id}."
727
+ )
728
+ return f"Successfully added {success} Items. {attempted - success} errors occurred."
713
729
 
714
- return None
715
- else:
716
- item = await self.database.prep_create_item(item=item, base_url=base_url)
717
- await self.database.create_item(item, refresh=kwargs.get("refresh", False))
718
- return ItemSerializer.db_to_stac(item, base_url)
730
+ # Handle single item
731
+ await self.database.create_item(
732
+ item_dict,
733
+ refresh=kwargs.get("refresh", False),
734
+ base_url=base_url,
735
+ exist_ok=False,
736
+ )
737
+ return ItemSerializer.db_to_stac(item_dict, base_url)
719
738
 
720
739
  @overrides
721
740
  async def update_item(
@@ -738,12 +757,12 @@ class TransactionsClient(AsyncBaseTransactionsClient):
738
757
  """
739
758
  item = item.model_dump(mode="json")
740
759
  base_url = str(kwargs["request"].base_url)
741
- now = datetime_type.now(timezone.utc).isoformat().replace("+00:00", "Z")
760
+ now = datetime_type.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
742
761
  item["properties"]["updated"] = now
743
762
 
744
- await self.database.check_collection_exists(collection_id)
745
- await self.delete_item(item_id=item_id, collection_id=collection_id)
746
- await self.create_item(collection_id=collection_id, item=Item(**item), **kwargs)
763
+ await self.database.create_item(
764
+ item, refresh=kwargs.get("refresh", False), base_url=base_url, exist_ok=True
765
+ )
747
766
 
748
767
  return ItemSerializer.db_to_stac(item, base_url)
749
768
 
@@ -876,7 +895,7 @@ class BulkTransactionsClient(BaseBulkTransactionsClient):
876
895
  The preprocessed item.
877
896
  """
878
897
  exist_ok = method == BulkTransactionMethod.UPSERT
879
- return self.database.sync_prep_create_item(
898
+ return self.database.bulk_sync_prep_create_item(
880
899
  item=item, base_url=base_url, exist_ok=exist_ok
881
900
  )
882
901
 
@@ -900,19 +919,32 @@ class BulkTransactionsClient(BaseBulkTransactionsClient):
900
919
  else:
901
920
  base_url = ""
902
921
 
903
- processed_items = [
904
- self.preprocess_item(item, base_url, items.method)
905
- for item in items.items.values()
906
- ]
922
+ processed_items = []
923
+ for item in items.items.values():
924
+ try:
925
+ validated = Item(**item) if not isinstance(item, Item) else item
926
+ processed_items.append(
927
+ self.preprocess_item(
928
+ validated.model_dump(mode="json"), base_url, items.method
929
+ )
930
+ )
931
+ except ValidationError:
932
+ # Immediately raise on the first invalid item (strict mode)
933
+ raise
907
934
 
908
- # not a great way to get the collection_id-- should be part of the method signature
909
935
  collection_id = processed_items[0]["collection"]
910
-
911
- self.database.bulk_sync(
912
- collection_id, processed_items, refresh=kwargs.get("refresh", False)
936
+ attempted = len(processed_items)
937
+ success, errors = self.database.bulk_sync(
938
+ collection_id,
939
+ processed_items,
940
+ refresh=kwargs.get("refresh", False),
913
941
  )
942
+ if errors:
943
+ logger.error(f"Bulk sync operation encountered errors: {errors}")
944
+ else:
945
+ logger.info(f"Bulk sync operation succeeded with {success} actions.")
914
946
 
915
- return f"Successfully added {len(processed_items)} Items."
947
+ return f"Successfully added/updated {success} Items. {attempted - success} errors occurred."
916
948
 
917
949
 
918
950
  _DEFAULT_QUERYABLES: Dict[str, Dict[str, Any]] = {
@@ -96,7 +96,13 @@ ES_MAPPINGS_DYNAMIC_TEMPLATES = [
96
96
  },
97
97
  # Default all other strings not otherwise specified to keyword
98
98
  {"strings": {"match_mapping_type": "string", "mapping": {"type": "keyword"}}},
99
- {"numerics": {"match_mapping_type": "long", "mapping": {"type": "float"}}},
99
+ {"long_to_double": {"match_mapping_type": "long", "mapping": {"type": "double"}}},
100
+ {
101
+ "double_to_double": {
102
+ "match_mapping_type": "double",
103
+ "mapping": {"type": "double"},
104
+ }
105
+ },
100
106
  ]
101
107
 
102
108
  ES_ITEMS_MAPPINGS = {
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "4.0.0a2"
2
+ __version__ = "4.1.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi-core
3
- Version: 4.0.0a2
3
+ Version: 4.1.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
@@ -144,7 +144,8 @@ You can customize additional settings in your `.env` file:
144
144
  | `BACKEND` | Tests-related variable | `elasticsearch` or `opensearch` based on the backend | Optional |
145
145
  | `ELASTICSEARCH_VERSION` | Version of Elasticsearch to use. | `8.11.0` | Optional |
146
146
  | `ENABLE_DIRECT_RESPONSE` | Enable direct response for maximum performance (disables all FastAPI dependencies, including authentication, custom status codes, and validation) | `false` | Optional |
147
- | `OPENSEARCH_VERSION` | OpenSearch version | `2.11.1` | Optional |
147
+ | `OPENSEARCH_VERSION` | OpenSearch version | `2.11.1` | Optional
148
+ | `RAISE_ON_BULK_ERROR` | Controls whether bulk insert operations raise exceptions on errors. If set to `true`, the operation will stop and raise an exception when an error occurs. If set to `false`, errors will be logged, and the operation will continue. **Note:** STAC Item and ItemCollection validation errors will always raise, regardless of this flag. | `false` | Optional |
148
149
 
149
150
  > [!NOTE]
150
151
  > The variables `ES_HOST`, `ES_PORT`, `ES_USE_SSL`, and `ES_VERIFY_CERTS` apply to both Elasticsearch and OpenSearch backends, so there is no need to rename the key names to `OS_` even if you're using OpenSearch.
@@ -2,15 +2,15 @@ stac_fastapi/core/__init__.py,sha256=8izV3IWRGdXmDOK1hIPQAanbWs9EI04PJCGgqG1ZGIs
2
2
  stac_fastapi/core/base_database_logic.py,sha256=GfxPMtg2gHAZM44haIgi_9J-IG1et_FYA5xRBosJpJA,1608
3
3
  stac_fastapi/core/base_settings.py,sha256=R3_Sx7n5XpGMs3zAwFJD7y008WvGU_uI2xkaabm82Kg,239
4
4
  stac_fastapi/core/basic_auth.py,sha256=RhFv3RVSHF6OaqnaaU2DO4ncJ_S5nB1q8UNpnVJJsrk,2155
5
- stac_fastapi/core/core.py,sha256=v0Y5DTtvUS5MdXBf19_9CYsgn9yjT68n16g_Ny3I-5E,39836
6
- stac_fastapi/core/database_logic.py,sha256=2DDHZ0ja1emXmrRzoW_Me38FPb30GjRFz2Dhe9zMMpI,6904
5
+ stac_fastapi/core/core.py,sha256=6mJBCSufrkOmJwjcLB2HYSmY9ocG946QPyw0zgZK6gI,41334
6
+ stac_fastapi/core/database_logic.py,sha256=KXBNL47QcqYsmtRrHjKaH3r_A4Wl4DUpumSDiuj4uVU,7051
7
7
  stac_fastapi/core/datetime_utils.py,sha256=ICUHPgvbH-xumIKdKVtcXogaCNEsxoaVqhWlVoCx-Ug,1481
8
8
  stac_fastapi/core/rate_limit.py,sha256=Gu8dAaJReGsj1L91U6m2tflU6RahpXDRs2-AYSKoybA,1318
9
9
  stac_fastapi/core/route_dependencies.py,sha256=zefYlfQTMW292vdqmtquW4UswtBHwH5Pm-8UynyZbJQ,5522
10
10
  stac_fastapi/core/serializers.py,sha256=pJjpwA6BOHjCXBCmwVTH7MOmTjY9NXF1-i_E0yB60Zg,6228
11
11
  stac_fastapi/core/session.py,sha256=Qr080UU_7qKtIv0qZAuOV7oNUQUzT5Yn00h-m_aoCvY,473
12
12
  stac_fastapi/core/utilities.py,sha256=493rYGimjWykrkWnRia1Aquc4Jvlyvio21VZcEmdxPo,6743
13
- stac_fastapi/core/version.py,sha256=GsmpaQvFvJbtYx8dw8vx3_8haCi-G94TpDhDpEjX-8E,47
13
+ stac_fastapi/core/version.py,sha256=3xU5aBmxgAcPizRlef_YROCW9ULsGOA4flSyd9AQog4,45
14
14
  stac_fastapi/core/extensions/__init__.py,sha256=2MCo0UoInkgItIM8id-rbeygzn_qUOvTGfr8jFXZjHQ,167
15
15
  stac_fastapi/core/extensions/aggregation.py,sha256=H5Yzvs-QH60P8jJm08Ng5FDvMU1o77WoNNZ2mKxUFjI,23062
16
16
  stac_fastapi/core/extensions/fields.py,sha256=NCT5XHvfaf297eDPNaIFsIzvJnbbUTpScqF0otdx0NA,1066
@@ -19,7 +19,7 @@ stac_fastapi/core/extensions/query.py,sha256=Xmo8pfZEZKPudZEjjozv3R0wLOP0ayjC9E6
19
19
  stac_fastapi/core/models/__init__.py,sha256=g-D1DiGfmC9Bg27DW9JzkN6fAvscv75wyhyiZ6NzvIk,48
20
20
  stac_fastapi/core/models/links.py,sha256=3jk4t2wA3RGTq9_BbzFsMKvMbgDBajQy4vKZFSHt7E8,6666
21
21
  stac_fastapi/core/models/search.py,sha256=7SgAUyzHGXBXSqB4G6cwq9FMwoAS00momb7jvBkjyow,27
22
- stac_fastapi_core-4.0.0a2.dist-info/METADATA,sha256=y1XXMZGFyr30LDpVkWnko94Az0mzaCkqYqeUfVzg8oQ,20120
23
- stac_fastapi_core-4.0.0a2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
24
- stac_fastapi_core-4.0.0a2.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
25
- stac_fastapi_core-4.0.0a2.dist-info/RECORD,,
22
+ stac_fastapi_core-4.1.0.dist-info/METADATA,sha256=GZZtIG4nc-v9LRmEN1XAdcqRMF1IrVMBzfuRDoc9vDc,20524
23
+ stac_fastapi_core-4.1.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
24
+ stac_fastapi_core-4.1.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
25
+ stac_fastapi_core-4.1.0.dist-info/RECORD,,