stac-fastapi-core 5.0.0a0__py3-none-any.whl → 6.0.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.
@@ -1,7 +1,7 @@
1
1
  """Base database logic."""
2
2
 
3
3
  import abc
4
- from typing import Any, Dict, Iterable, Optional
4
+ from typing import Any, Dict, Iterable, List, Optional
5
5
 
6
6
 
7
7
  class BaseDatabaseLogic(abc.ABC):
@@ -29,6 +29,30 @@ class BaseDatabaseLogic(abc.ABC):
29
29
  """Create an item in the database."""
30
30
  pass
31
31
 
32
+ @abc.abstractmethod
33
+ async def merge_patch_item(
34
+ self,
35
+ collection_id: str,
36
+ item_id: str,
37
+ item: Dict,
38
+ base_url: str,
39
+ refresh: bool = True,
40
+ ) -> Dict:
41
+ """Patch a item in the database follows RF7396."""
42
+ pass
43
+
44
+ @abc.abstractmethod
45
+ async def json_patch_item(
46
+ self,
47
+ collection_id: str,
48
+ item_id: str,
49
+ operations: List,
50
+ base_url: str,
51
+ refresh: bool = True,
52
+ ) -> Dict:
53
+ """Patch a item in the database follows RF6902."""
54
+ pass
55
+
32
56
  @abc.abstractmethod
33
57
  async def delete_item(
34
58
  self, item_id: str, collection_id: str, refresh: bool = False
@@ -36,11 +60,45 @@ class BaseDatabaseLogic(abc.ABC):
36
60
  """Delete an item from the database."""
37
61
  pass
38
62
 
63
+ @abc.abstractmethod
64
+ async def get_items_mapping(self, collection_id: str) -> Dict[str, Dict[str, Any]]:
65
+ """Get the mapping for the items in the collection."""
66
+ pass
67
+
68
+ @abc.abstractmethod
69
+ async def get_items_unique_values(
70
+ self, collection_id: str, field_names: Iterable[str], *, limit: int = ...
71
+ ) -> Dict[str, List[str]]:
72
+ """Get the unique values for the given fields in the collection."""
73
+ pass
74
+
39
75
  @abc.abstractmethod
40
76
  async def create_collection(self, collection: Dict, refresh: bool = False) -> None:
41
77
  """Create a collection in the database."""
42
78
  pass
43
79
 
80
+ @abc.abstractmethod
81
+ async def merge_patch_collection(
82
+ self,
83
+ collection_id: str,
84
+ collection: Dict,
85
+ base_url: str,
86
+ refresh: bool = True,
87
+ ) -> Dict:
88
+ """Patch a collection in the database follows RF7396."""
89
+ pass
90
+
91
+ @abc.abstractmethod
92
+ async def json_patch_collection(
93
+ self,
94
+ collection_id: str,
95
+ operations: List,
96
+ base_url: str,
97
+ refresh: bool = True,
98
+ ) -> Dict:
99
+ """Patch a collection in the database follows RF6902."""
100
+ pass
101
+
44
102
  @abc.abstractmethod
45
103
  async def find_collection(self, collection_id: str) -> Dict:
46
104
  """Find a collection in the database."""
stac_fastapi/core/core.py CHANGED
@@ -11,7 +11,7 @@ import attr
11
11
  import orjson
12
12
  from fastapi import HTTPException, Request
13
13
  from overrides import overrides
14
- from pydantic import ValidationError
14
+ from pydantic import TypeAdapter, ValidationError
15
15
  from pygeofilter.backends.cql2_json import to_cql2
16
16
  from pygeofilter.parsers.cql2_text import parse as parse_cql2_text
17
17
  from stac_pydantic import Collection, Item, ItemCollection
@@ -26,6 +26,12 @@ from stac_fastapi.core.models.links import PagingLinks
26
26
  from stac_fastapi.core.serializers import CollectionSerializer, ItemSerializer
27
27
  from stac_fastapi.core.session import Session
28
28
  from stac_fastapi.core.utilities import filter_fields
29
+ from stac_fastapi.extensions.core.transaction import AsyncBaseTransactionsClient
30
+ from stac_fastapi.extensions.core.transaction.request import (
31
+ PartialCollection,
32
+ PartialItem,
33
+ PatchOperation,
34
+ )
29
35
  from stac_fastapi.extensions.third_party.bulk_transactions import (
30
36
  BaseBulkTransactionsClient,
31
37
  BulkTransactionMethod,
@@ -33,13 +39,16 @@ from stac_fastapi.extensions.third_party.bulk_transactions import (
33
39
  )
34
40
  from stac_fastapi.types import stac as stac_types
35
41
  from stac_fastapi.types.conformance import BASE_CONFORMANCE_CLASSES
36
- from stac_fastapi.types.core import AsyncBaseCoreClient, AsyncBaseTransactionsClient
42
+ from stac_fastapi.types.core import AsyncBaseCoreClient
37
43
  from stac_fastapi.types.extension import ApiExtension
38
44
  from stac_fastapi.types.requests import get_base_url
39
45
  from stac_fastapi.types.search import BaseSearchPostRequest
40
46
 
41
47
  logger = logging.getLogger(__name__)
42
48
 
49
+ partialItemValidator = TypeAdapter(PartialItem)
50
+ partialCollectionValidator = TypeAdapter(PartialCollection)
51
+
43
52
 
44
53
  @attr.s
45
54
  class CoreClient(AsyncBaseCoreClient):
@@ -680,6 +689,63 @@ class TransactionsClient(AsyncBaseTransactionsClient):
680
689
 
681
690
  return ItemSerializer.db_to_stac(item, base_url)
682
691
 
692
+ @overrides
693
+ async def patch_item(
694
+ self,
695
+ collection_id: str,
696
+ item_id: str,
697
+ patch: Union[PartialItem, List[PatchOperation]],
698
+ **kwargs,
699
+ ):
700
+ """Patch an item in the collection.
701
+
702
+ Args:
703
+ collection_id (str): The ID of the collection the item belongs to.
704
+ item_id (str): The ID of the item to be updated.
705
+ patch (Union[PartialItem, List[PatchOperation]]): The item data or operations.
706
+ kwargs: Other optional arguments, including the request object.
707
+
708
+ Returns:
709
+ stac_types.Item: The updated item object.
710
+
711
+ Raises:
712
+ NotFound: If the specified collection is not found in the database.
713
+
714
+ """
715
+ base_url = str(kwargs["request"].base_url)
716
+
717
+ content_type = kwargs["request"].headers.get("content-type")
718
+
719
+ item = None
720
+ if isinstance(patch, list) and content_type == "application/json-patch+json":
721
+ item = await self.database.json_patch_item(
722
+ collection_id=collection_id,
723
+ item_id=item_id,
724
+ operations=patch,
725
+ base_url=base_url,
726
+ )
727
+
728
+ if isinstance(patch, dict):
729
+ patch = partialItemValidator.validate_python(patch)
730
+
731
+ if isinstance(patch, PartialItem) and content_type in [
732
+ "application/merge-patch+json",
733
+ "application/json",
734
+ ]:
735
+ item = await self.database.merge_patch_item(
736
+ collection_id=collection_id,
737
+ item_id=item_id,
738
+ item=patch,
739
+ base_url=base_url,
740
+ )
741
+
742
+ if item:
743
+ return ItemSerializer.db_to_stac(item, base_url=base_url)
744
+
745
+ raise NotImplementedError(
746
+ f"Content-Type: {content_type} and body: {patch} combination not implemented"
747
+ )
748
+
683
749
  @overrides
684
750
  async def delete_item(self, item_id: str, collection_id: str, **kwargs) -> None:
685
751
  """Delete an item from a collection.
@@ -761,6 +827,59 @@ class TransactionsClient(AsyncBaseTransactionsClient):
761
827
  extensions=[type(ext).__name__ for ext in self.database.extensions],
762
828
  )
763
829
 
830
+ @overrides
831
+ async def patch_collection(
832
+ self,
833
+ collection_id: str,
834
+ patch: Union[PartialCollection, List[PatchOperation]],
835
+ **kwargs,
836
+ ):
837
+ """Update a collection.
838
+
839
+ Called with `PATCH /collections/{collection_id}`
840
+
841
+ Args:
842
+ collection_id: id of the collection.
843
+ patch: either the partial collection or list of patch operations.
844
+
845
+ Returns:
846
+ The patched collection.
847
+ """
848
+ base_url = str(kwargs["request"].base_url)
849
+ content_type = kwargs["request"].headers.get("content-type")
850
+
851
+ collection = None
852
+ if isinstance(patch, list) and content_type == "application/json-patch+json":
853
+ collection = await self.database.json_patch_collection(
854
+ collection_id=collection_id,
855
+ operations=patch,
856
+ base_url=base_url,
857
+ )
858
+
859
+ if isinstance(patch, dict):
860
+ patch = partialCollectionValidator.validate_python(patch)
861
+
862
+ if isinstance(patch, PartialCollection) and content_type in [
863
+ "application/merge-patch+json",
864
+ "application/json",
865
+ ]:
866
+ collection = await self.database.merge_patch_collection(
867
+ collection_id=collection_id,
868
+ collection=patch,
869
+ base_url=base_url,
870
+ )
871
+
872
+ if collection:
873
+ return CollectionSerializer.db_to_stac(
874
+ collection,
875
+ kwargs["request"],
876
+ extensions=[type(ext).__name__ for ext in self.database.extensions],
877
+ )
878
+
879
+ raise NotImplementedError(
880
+ f"Content-Type: {content_type} and body: {patch} combination not implemented"
881
+ )
882
+
764
883
  @overrides
765
884
  async def delete_collection(self, collection_id: str, **kwargs) -> None:
766
885
  """
@@ -60,6 +60,17 @@ DEFAULT_QUERYABLES: Dict[str, Dict[str, Any]] = {
60
60
  "maximum": 100,
61
61
  },
62
62
  }
63
+ """Queryables that are present in all collections."""
64
+
65
+ OPTIONAL_QUERYABLES: Dict[str, Dict[str, Any]] = {
66
+ "platform": {
67
+ "$enum": True,
68
+ "description": "Satellite platform identifier",
69
+ },
70
+ }
71
+ """Queryables that are present in some collections."""
72
+
73
+ ALL_QUERYABLES: Dict[str, Dict[str, Any]] = DEFAULT_QUERYABLES | OPTIONAL_QUERYABLES
63
74
 
64
75
 
65
76
  class LogicalOp(str, Enum):
@@ -2,11 +2,11 @@
2
2
 
3
3
  import importlib
4
4
  import inspect
5
- import json
6
5
  import logging
7
6
  import os
8
7
  from typing import List
9
8
 
9
+ import orjson
10
10
  from fastapi import Depends
11
11
  from jsonschema import validate
12
12
 
@@ -84,14 +84,14 @@ route_dependencies_schema = {
84
84
 
85
85
  def get_route_dependencies_conf(route_dependencies_env: str) -> list:
86
86
  """Get Route dependencies configuration from file or environment variable."""
87
- if os.path.exists(route_dependencies_env):
88
- with open(route_dependencies_env, encoding="utf-8") as route_dependencies_file:
89
- route_dependencies_conf = json.load(route_dependencies_file)
87
+ if os.path.isfile(route_dependencies_env):
88
+ with open(route_dependencies_env, "rb") as f:
89
+ route_dependencies_conf = orjson.loads(f.read())
90
90
 
91
91
  else:
92
92
  try:
93
- route_dependencies_conf = json.loads(route_dependencies_env)
94
- except json.JSONDecodeError as exception:
93
+ route_dependencies_conf = orjson.loads(route_dependencies_env)
94
+ except orjson.JSONDecodeError as exception:
95
95
  _LOGGER.error("Invalid JSON format for route dependencies. %s", exception)
96
96
  raise
97
97
 
@@ -3,6 +3,7 @@
3
3
  This module contains functions for transforming geospatial coordinates,
4
4
  such as converting bounding boxes to polygon representations.
5
5
  """
6
+
6
7
  import logging
7
8
  import os
8
9
  from typing import Any, Dict, List, Optional, Set, Union
@@ -1,2 +1,2 @@
1
1
  """library version."""
2
- __version__ = "5.0.0a0"
2
+ __version__ = "6.0.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stac-fastapi-core
3
- Version: 5.0.0a0
3
+ Version: 6.0.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,10 +18,10 @@ Description-Content-Type: text/markdown
18
18
  Requires-Dist: fastapi~=0.109.0
19
19
  Requires-Dist: attrs>=23.2.0
20
20
  Requires-Dist: pydantic<3.0.0,>=2.4.1
21
- Requires-Dist: stac-pydantic~=3.1.0
22
- Requires-Dist: stac-fastapi.api==5.2.0
23
- Requires-Dist: stac-fastapi.extensions==5.2.0
24
- Requires-Dist: stac-fastapi.types==5.2.0
21
+ Requires-Dist: stac-pydantic~=3.3.0
22
+ Requires-Dist: stac-fastapi.types==6.0.0
23
+ Requires-Dist: stac-fastapi.api==6.0.0
24
+ Requires-Dist: stac-fastapi.extensions==6.0.0
25
25
  Requires-Dist: orjson~=3.9.0
26
26
  Requires-Dist: overrides~=7.4.0
27
27
  Requires-Dist: geojson-pydantic~=1.0.0
@@ -1,24 +1,24 @@
1
1
  stac_fastapi/core/__init__.py,sha256=8izV3IWRGdXmDOK1hIPQAanbWs9EI04PJCGgqG1ZGIs,20
2
- stac_fastapi/core/base_database_logic.py,sha256=GfxPMtg2gHAZM44haIgi_9J-IG1et_FYA5xRBosJpJA,1608
2
+ stac_fastapi/core/base_database_logic.py,sha256=AcvS38fWUk44BHD1vJfQENKHx1LDQdvRjMqcSQBFqw4,3189
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=oFDJw7EOLGtkRX-msm4W6DaDQz_P4CgZo1TFHOGy4kI,32064
5
+ stac_fastapi/core/core.py,sha256=Unfg3c7UX_AxH_FdJSKfHzIggWhENUR2PAlf7XKbgew,35974
6
6
  stac_fastapi/core/datetime_utils.py,sha256=oueonjfuKdsB8IF_Ow-brK_X1bXs8_GLuVZOZr_L4ws,2527
7
7
  stac_fastapi/core/rate_limit.py,sha256=Gu8dAaJReGsj1L91U6m2tflU6RahpXDRs2-AYSKoybA,1318
8
- stac_fastapi/core/route_dependencies.py,sha256=zefYlfQTMW292vdqmtquW4UswtBHwH5Pm-8UynyZbJQ,5522
8
+ stac_fastapi/core/route_dependencies.py,sha256=hdtuMkv-zY1vg0YxiCz1aKP0SbBcORqDGEKDGgEazW8,5482
9
9
  stac_fastapi/core/serializers.py,sha256=pJjpwA6BOHjCXBCmwVTH7MOmTjY9NXF1-i_E0yB60Zg,6228
10
10
  stac_fastapi/core/session.py,sha256=Qr080UU_7qKtIv0qZAuOV7oNUQUzT5Yn00h-m_aoCvY,473
11
- stac_fastapi/core/utilities.py,sha256=n8bK6EB3ZPl16uKtXhAVhlDiPkm9iRjRKJDzFn3ddP4,7295
12
- stac_fastapi/core/version.py,sha256=c2gZCryeGGpiwcZ3c6UrJnUyJqZkYSBi5EhoLK_wRU0,47
11
+ stac_fastapi/core/utilities.py,sha256=xXWO5oJCNDi7_C5jPYlHZD0B-DL-FN66eEUBUSW-cXw,7296
12
+ stac_fastapi/core/version.py,sha256=Fo5UFEQVxJZ3nywa3IY-enu5UQBE0X45nrQaRBe8c9o,45
13
13
  stac_fastapi/core/extensions/__init__.py,sha256=2MCo0UoInkgItIM8id-rbeygzn_qUOvTGfr8jFXZjHQ,167
14
14
  stac_fastapi/core/extensions/aggregation.py,sha256=v1hUHqlYuMqfQ554g3cTp16pUyRYucQxPERbHPAFtf8,1878
15
15
  stac_fastapi/core/extensions/fields.py,sha256=NCT5XHvfaf297eDPNaIFsIzvJnbbUTpScqF0otdx0NA,1066
16
- stac_fastapi/core/extensions/filter.py,sha256=QYsu8hUyqZDKycpMkKOAAW7Vd7KVl2Q-1k3mX-aWQI0,3246
16
+ stac_fastapi/core/extensions/filter.py,sha256=mQKQYKJk4lkKZs9C3rkNR6ICTuKedokWiRBIXii20jc,3599
17
17
  stac_fastapi/core/extensions/query.py,sha256=Xmo8pfZEZKPudZEjjozv3R0wLOP0ayjC9E67sBOXqWY,1803
18
18
  stac_fastapi/core/models/__init__.py,sha256=g-D1DiGfmC9Bg27DW9JzkN6fAvscv75wyhyiZ6NzvIk,48
19
19
  stac_fastapi/core/models/links.py,sha256=3jk4t2wA3RGTq9_BbzFsMKvMbgDBajQy4vKZFSHt7E8,6666
20
20
  stac_fastapi/core/models/search.py,sha256=7SgAUyzHGXBXSqB4G6cwq9FMwoAS00momb7jvBkjyow,27
21
- stac_fastapi_core-5.0.0a0.dist-info/METADATA,sha256=xOvtyJeKf2tAsR5NGShbP9L69yc6Ms83HW1_kxAk7Zc,31595
22
- stac_fastapi_core-5.0.0a0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
23
- stac_fastapi_core-5.0.0a0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
24
- stac_fastapi_core-5.0.0a0.dist-info/RECORD,,
21
+ stac_fastapi_core-6.0.0.dist-info/METADATA,sha256=yP22yFDPXvFZQh_zzAltsue6u7GGsiaClwrbxeMuZU4,31593
22
+ stac_fastapi_core-6.0.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
23
+ stac_fastapi_core-6.0.0.dist-info/top_level.txt,sha256=vqn-D9-HsRPTTxy0Vk_KkDmTiMES4owwBQ3ydSZYb2s,13
24
+ stac_fastapi_core-6.0.0.dist-info/RECORD,,