|
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 | 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,
|
32 | 38 | Items,
|
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 @@ async def update_item(
|
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 @@ async def update_collection(
|
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 | """
|
|
0 commit comments