Skip to content

move transaction clients and models to the extensions module #846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

### Changed

* Add Item and Collection `PATCH` endpoints with support for [RFC 6902](https://tools.ietf.org/html/rfc6902) and [RFC 7396](https://tools.ietf.org/html/rfc7386) in the `TransactionExtension`
- Add Item and Collection `PATCH` endpoints with support for [RFC 6902](https://tools.ietf.org/html/rfc6902) and [RFC 7396](https://tools.ietf.org/html/rfc7386) in the `TransactionExtension`
- remove support of `cql-json` in Filter extension ([#840](https://github.com/stac-utils/stac-fastapi/pull/840))
- move `transaction` clients and models to `stac_fastapi.extension` sub-module

### Fixed

Expand Down
3 changes: 2 additions & 1 deletion stac_fastapi/api/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from stac_fastapi.api.app import StacApi
from stac_fastapi.api.models import ItemCollectionUri, create_request_model
from stac_fastapi.extensions.core import TokenPaginationExtension, TransactionExtension
from stac_fastapi.extensions.core.transaction import BaseTransactionsClient
from stac_fastapi.types import config, core


Expand Down Expand Up @@ -418,7 +419,7 @@ def item_collection(self, *args, **kwargs):
...


class DummyTransactionsClient(core.BaseTransactionsClient):
class DummyTransactionsClient(BaseTransactionsClient):
"""Defines a pattern for implementing the STAC transaction extension."""

def create_item(self, *args, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""transaction extension module."""

from .client import AsyncBaseTransactionsClient, BaseTransactionsClient
from .transaction import TransactionConformanceClasses, TransactionExtension

__all__ = [
"AsyncBaseTransactionsClient",
"BaseTransactionsClient",
"TransactionExtension",
"TransactionConformanceClasses",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
"""Transaction clients."""

import abc
from typing import List, Optional, Union

import attr
from stac_pydantic import Collection, Item, ItemCollection
from starlette.responses import Response

from stac_fastapi.types import stac

from .request import PartialCollection, PartialItem, PatchOperation


@attr.s # type:ignore
class BaseTransactionsClient(abc.ABC):
"""Defines a pattern for implementing the STAC API Transaction Extension."""

@abc.abstractmethod
def create_item(
self,
collection_id: str,
item: Union[Item, ItemCollection],
**kwargs,
) -> Optional[Union[stac.Item, Response, None]]:
"""Create a new item.

Called with `POST /collections/{collection_id}/items`.

Args:
item: the item or item collection
collection_id: the id of the collection from the resource path

Returns:
The item that was created or None if item collection.
"""
...

@abc.abstractmethod
def update_item(
self, collection_id: str, item_id: str, item: Item, **kwargs
) -> Optional[Union[stac.Item, Response]]:
"""Perform a complete update on an existing item.

Called with `PUT /collections/{collection_id}/items`. It is expected
that this item already exists. The update should do a diff against the
saved item and perform any necessary updates. Partial updates are not
supported by the transactions extension.

Args:
item: the item (must be complete)
collection_id: the id of the collection from the resource path

Returns:
The updated item.
"""
...

@abc.abstractmethod
def patch_item(
self,
collection_id: str,
item_id: str,
patch: Union[PartialItem, List[PatchOperation]],
**kwargs,
) -> Optional[Union[stac.Item, Response]]:
"""Update an item from a collection.

Called with `PATCH /collections/{collection_id}/items/{item_id}`

example:
# convert merge patch item to list of operations
if isinstance(patch, PartialItem):
patch = patch.operations()

item = backend.patch_item(collection_id, item_id, patch)

return item

Args:
item_id: id of the item.
collection_id: id of the collection.
patch: either the partial item or list of patch operations.

Returns:
The patched item.
"""
...

@abc.abstractmethod
def delete_item(
self, item_id: str, collection_id: str, **kwargs
) -> Optional[Union[stac.Item, Response]]:
"""Delete an item from a collection.

Called with `DELETE /collections/{collection_id}/items/{item_id}`

Args:
item_id: id of the item.
collection_id: id of the collection.

Returns:
The deleted item.
"""
...

@abc.abstractmethod
def create_collection(
self, collection: Collection, **kwargs
) -> Optional[Union[stac.Collection, Response]]:
"""Create a new collection.

Called with `POST /collections`.

Args:
collection: the collection

Returns:
The collection that was created.
"""
...

@abc.abstractmethod
def update_collection(
self, collection_id: str, collection: Collection, **kwargs
) -> Optional[Union[stac.Collection, Response]]:
"""Perform a complete update on an existing collection.

Called with `PUT /collections/{collection_id}`. It is expected that this
collection already exists. The update should do a diff against the saved
collection and perform any necessary updates. Partial updates are not
supported by the transactions extension.

Args:
collection_id: id of the existing collection to be updated
collection: the updated collection (must be complete)

Returns:
The updated collection.
"""
...

@abc.abstractmethod
def patch_collection(
self,
collection_id: str,
patch: Union[PartialCollection, List[PatchOperation]],
**kwargs,
) -> Optional[Union[stac.Collection, Response]]:
"""Update a collection.

Called with `PATCH /collections/{collection_id}`

example:
# convert merge patch item to list of operations
if isinstance(patch, PartialCollection):
patch = patch.operations()

collection = backend.patch_collection(collection_id, patch)

return collection

Args:
collection_id: id of the collection.
patch: either the partial collection or list of patch operations.

Returns:
The patched collection.
"""
...

@abc.abstractmethod
def delete_collection(
self, collection_id: str, **kwargs
) -> Optional[Union[stac.Collection, Response]]:
"""Delete a collection.

Called with `DELETE /collections/{collection_id}`

Args:
collection_id: id of the collection.

Returns:
The deleted collection.
"""
...


@attr.s # type:ignore
class AsyncBaseTransactionsClient(abc.ABC):
"""Defines a pattern for implementing the STAC transaction extension."""

@abc.abstractmethod
async def create_item(
self,
collection_id: str,
item: Union[Item, ItemCollection],
**kwargs,
) -> Optional[Union[stac.Item, Response, None]]:
"""Create a new item.

Called with `POST /collections/{collection_id}/items`.

Args:
item: the item or item collection
collection_id: the id of the collection from the resource path

Returns:
The item that was created or None if item collection.
"""
...

@abc.abstractmethod
async def update_item(
self, collection_id: str, item_id: str, item: Item, **kwargs
) -> Optional[Union[stac.Item, Response]]:
"""Perform a complete update on an existing item.

Called with `PUT /collections/{collection_id}/items`. It is expected
that this item already exists. The update should do a diff against the
saved item and perform any necessary updates. Partial updates are not
supported by the transactions extension.

Args:
item: the item (must be complete)

Returns:
The updated item.
"""
...

@abc.abstractmethod
async def patch_item(
self,
collection_id: str,
item_id: str,
patch: Union[PartialItem, List[PatchOperation]],
**kwargs,
) -> Optional[Union[stac.Item, Response]]:
"""Update an item from a collection.

Called with `PATCH /collections/{collection_id}/items/{item_id}`

example:
# convert merge patch item to list of operations
if isinstance(patch, PartialItem):
patch = patch.operations()

item = backend.patch_item(collection_id, item_id, patch)

return item

Args:
item_id: id of the item.
collection_id: id of the collection.
patch: either the partial item or list of patch operations.

Returns:
The patched item.
"""
...

@abc.abstractmethod
async def delete_item(
self, item_id: str, collection_id: str, **kwargs
) -> Optional[Union[stac.Item, Response]]:
"""Delete an item from a collection.

Called with `DELETE /collections/{collection_id}/items/{item_id}`

Args:
item_id: id of the item.
collection_id: id of the collection.

Returns:
The deleted item.
"""
...

@abc.abstractmethod
async def create_collection(
self, collection: Collection, **kwargs
) -> Optional[Union[stac.Collection, Response]]:
"""Create a new collection.

Called with `POST /collections`.

Args:
collection: the collection

Returns:
The collection that was created.
"""
...

@abc.abstractmethod
async def update_collection(
self, collection_id: str, collection: Collection, **kwargs
) -> Optional[Union[stac.Collection, Response]]:
"""Perform a complete update on an existing collection.

Called with `PUT /collections/{collection_id}`. It is expected that this item
already exists. The update should do a diff against the saved collection and
perform any necessary updates. Partial updates are not supported by the
transactions extension.

Args:
collection_id: id of the existing collection to be updated
collection: the updated collection (must be complete)

Returns:
The updated collection.
"""
...

@abc.abstractmethod
async def patch_collection(
self,
collection_id: str,
patch: Union[PartialCollection, List[PatchOperation]],
**kwargs,
) -> Optional[Union[stac.Collection, Response]]:
"""Update a collection.

Called with `PATCH /collections/{collection_id}`

example:
# convert merge patch item to list of operations
if isinstance(patch, PartialCollection):
patch = patch.operations()

collection = backend.patch_collection(collection_id, patch)

return collection

Args:
collection_id: id of the collection.
patch: either the partial collection or list of patch operations.

Returns:
The patched collection.
"""
...

@abc.abstractmethod
async def delete_collection(
self, collection_id: str, **kwargs
) -> Optional[Union[stac.Collection, Response]]:
"""Delete a collection.

Called with `DELETE /collections/{collection_id}`

Args:
collection_id: id of the collection.

Returns:
The deleted collection.
"""
...
Loading