Skip to content

Commit e0bd94f

Browse files
committed
Adding patch endpoints to transactions extension to elasticsearch.
1 parent 5d52698 commit e0bd94f

File tree

4 files changed

+440
-3
lines changed

4 files changed

+440
-3
lines changed

stac_fastapi/core/stac_fastapi/core/base_database_logic.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Base database logic."""
22

33
import abc
4-
from typing import Any, Dict, Iterable, Optional
4+
from typing import Any, Dict, Iterable, List, Optional
55

66

77
class BaseDatabaseLogic(abc.ABC):
@@ -29,6 +29,30 @@ async def create_item(self, item: Dict, refresh: bool = False) -> None:
2929
"""Create an item in the database."""
3030
pass
3131

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+
3256
@abc.abstractmethod
3357
async def delete_item(
3458
self, item_id: str, collection_id: str, refresh: bool = False
@@ -41,6 +65,28 @@ async def create_collection(self, collection: Dict, refresh: bool = False) -> No
4165
"""Create a collection in the database."""
4266
pass
4367

68+
@abc.abstractmethod
69+
async def merge_patch_collection(
70+
self,
71+
collection_id: str,
72+
collection: Dict,
73+
base_url: str,
74+
refresh: bool = True,
75+
) -> Dict:
76+
"""Patch a collection in the database follows RF7396."""
77+
pass
78+
79+
@abc.abstractmethod
80+
async def json_patch_collection(
81+
self,
82+
collection_id: str,
83+
operations: List,
84+
base_url: str,
85+
refresh: bool = True,
86+
) -> Dict:
87+
"""Patch a collection in the database follows RF6902."""
88+
pass
89+
4490
@abc.abstractmethod
4591
async def find_collection(self, collection_id: str) -> Dict:
4692
"""Find a collection in the database."""

stac_fastapi/core/stac_fastapi/core/core.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Core client."""
2+
23
import logging
34
import re
45
from datetime import datetime as datetime_type
@@ -708,6 +709,58 @@ async def update_item(
708709

709710
return ItemSerializer.db_to_stac(item, base_url)
710711

712+
@overrides
713+
async def merge_patch_item(
714+
self, collection_id: str, item_id: str, item: stac_types.PartialItem, **kwargs
715+
) -> Optional[stac_types.Item]:
716+
"""Patch an item in the collection following RF7396..
717+
718+
Args:
719+
collection_id (str): The ID of the collection the item belongs to.
720+
item_id (str): The ID of the item to be updated.
721+
item (stac_types.PartialItem): The partial item data.
722+
kwargs: Other optional arguments, including the request object.
723+
724+
Returns:
725+
stac_types.Item: The patched item object.
726+
727+
"""
728+
item = await self.database.merge_patch_item(
729+
collection_id=collection_id,
730+
item_id=item_id,
731+
item=item,
732+
base_url=str(kwargs["request"].base_url),
733+
)
734+
return ItemSerializer.db_to_stac(item, base_url=str(kwargs["request"].base_url))
735+
736+
@overrides
737+
async def json_patch_item(
738+
self,
739+
collection_id: str,
740+
item_id: str,
741+
operations: List[stac_types.PatchOperation],
742+
**kwargs,
743+
) -> Optional[stac_types.Item]:
744+
"""Patch an item in the collection following RF6902.
745+
746+
Args:
747+
collection_id (str): The ID of the collection the item belongs to.
748+
item_id (str): The ID of the item to be updated.
749+
operations (List): List of operations to run on item.
750+
kwargs: Other optional arguments, including the request object.
751+
752+
Returns:
753+
stac_types.Item: The patched item object.
754+
755+
"""
756+
item = await self.database.json_patch_item(
757+
collection_id=collection_id,
758+
item_id=item_id,
759+
base_url=str(kwargs["request"].base_url),
760+
operations=operations,
761+
)
762+
return ItemSerializer.db_to_stac(item, base_url=str(kwargs["request"].base_url))
763+
711764
@overrides
712765
async def delete_item(
713766
self, item_id: str, collection_id: str, **kwargs
@@ -788,6 +841,59 @@ async def update_collection(
788841
extensions=[type(ext).__name__ for ext in self.database.extensions],
789842
)
790843

844+
@overrides
845+
async def merge_patch_collection(
846+
self, collection_id: str, collection: stac_types.PartialCollection, **kwargs
847+
) -> Optional[stac_types.Collection]:
848+
"""Patch a collection following RF7396..
849+
850+
Args:
851+
collection_id (str): The ID of the collection to patch.
852+
collection (stac_types.Collection): The partial collection data.
853+
kwargs: Other optional arguments, including the request object.
854+
855+
Returns:
856+
stac_types.Collection: The patched collection object.
857+
858+
"""
859+
collection = await self.database.merge_patch_collection(
860+
collection_id=collection_id,
861+
base_url=str(kwargs["request"].base_url),
862+
collection=collection,
863+
)
864+
865+
return CollectionSerializer.db_to_stac(
866+
collection,
867+
kwargs["request"],
868+
extensions=[type(ext).__name__ for ext in self.database.extensions],
869+
)
870+
871+
@overrides
872+
async def json_patch_collection(
873+
self, collection_id: str, operations: List[stac_types.PatchOperation], **kwargs
874+
) -> Optional[stac_types.Collection]:
875+
"""Patch a collection following RF6902.
876+
877+
Args:
878+
collection_id (str): The ID of the collection to patch.
879+
operations (List): List of operations to run on collection.
880+
kwargs: Other optional arguments, including the request object.
881+
882+
Returns:
883+
stac_types.Collection: The patched collection object.
884+
885+
"""
886+
collection = await self.database.json_patch_collection(
887+
collection_id=collection_id,
888+
operations=operations,
889+
base_url=str(kwargs["request"].base_url),
890+
)
891+
return CollectionSerializer.db_to_stac(
892+
collection,
893+
kwargs["request"],
894+
extensions=[type(ext).__name__ for ext in self.database.extensions],
895+
)
896+
791897
@overrides
792898
async def delete_collection(
793899
self, collection_id: str, **kwargs

stac_fastapi/core/stac_fastapi/core/utilities.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
This module contains functions for transforming geospatial coordinates,
44
such as converting bounding boxes to polygon representations.
55
"""
6+
7+
import json
68
from typing import Any, Dict, List, Optional, Set, Union
79

810
from stac_fastapi.types.stac import Item
@@ -133,3 +135,64 @@ def dict_deep_update(merge_to: Dict[str, Any], merge_from: Dict[str, Any]) -> No
133135
dict_deep_update(merge_to[k], merge_from[k])
134136
else:
135137
merge_to[k] = v
138+
139+
140+
def merge_to_operations(data: Dict) -> List:
141+
"""Convert merge operation to list of RF6902 operations.
142+
143+
Args:
144+
data: dictionary to convert.
145+
146+
Returns:
147+
List: list of RF6902 operations.
148+
"""
149+
operations = []
150+
151+
for key, value in data.copy().items():
152+
153+
if value is None:
154+
operations.append({"op": "remove", "path": key})
155+
continue
156+
157+
elif isinstance(value, dict):
158+
nested_operations = merge_to_operations(value)
159+
160+
for nested_operation in nested_operations:
161+
nested_operation["path"] = f"{key}.{nested_operation['path']}"
162+
operations.append(nested_operation)
163+
164+
else:
165+
operations.append({"op": "add", "path": key, "value": value})
166+
167+
return operations
168+
169+
170+
def operations_to_script(operations: List) -> Dict:
171+
"""Convert list of operation to painless script.
172+
173+
Args:
174+
operations: List of RF6902 operations.
175+
176+
Returns:
177+
Dict: elasticsearch update script.
178+
"""
179+
source = ""
180+
for operation in operations:
181+
if operation["op"] in ["copy", "move"]:
182+
source += (
183+
f"ctx._source.{operation['path']} = ctx._source.{operation['from']};"
184+
)
185+
186+
if operation["op"] in ["remove", "move"]:
187+
nest, partition, key = operation["path"].rpartition(".")
188+
source += f"ctx._source.{nest + partition}remove('{key}');"
189+
190+
if operation["op"] in ["add", "replace"]:
191+
source += (
192+
f"ctx._source.{operation['path']} = {json.dumps(operation['value'])};"
193+
)
194+
195+
return {
196+
"source": source,
197+
"lang": "painless",
198+
}

0 commit comments

Comments
 (0)