Skip to content

Commit

Permalink
refactored conformance classes for extensions (#790)
Browse files Browse the repository at this point in the history
* refactored conformance classes for extensions

* update changelog
  • Loading branch information
vincentsarago authored Jan 29, 2025
1 parent 62ba40c commit 2fbcfc0
Show file tree
Hide file tree
Showing 17 changed files with 391 additions and 120 deletions.
16 changes: 16 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## [Unreleased]

### Changed

- refactored conformance classes for extensions

- renamed `collection_search.ConformanceClasses` -> `collection_search.CollectionSearchConformanceClasses`
- removed `FREETEXT`, `FILTER`, `QUERY`, `SORT` and `FIELDS` entries from the `CollectionSearchConformanceClasses` Enum (and moved to each extension's Enum)
- changed `collection_search.CollectionSearchPostExtension.from_extension(ext)` to use the conformance classes from the input extensions to derive the output conformance classes.
- added `fields.FieldsConformanceClasses` Enum
- renamed `filter.FilterConformanceClasses.FEATURES_FILTER` -> `filter.FilterConformanceClasses.ITEMS`
- renamed `filter.FilterConformanceClasses.ITEM_SEARCH_FILTER` -> `filter.FilterConformanceClasses.SEARCH`
- added `filter.FilterConformanceClasses.COLLECTIONS`
- added `filter.SearchFilterExtension`, `filter.ItemCollectionFilterExtension` and `filter.CollectionSearchFilterExtension` endpoint specific extensions
- removed `FreeTextConformanceClasses.COLLECTIONS` and `FreeTextConformanceClasses.ITEMS` in `FreeTextExtension` and `FreeTextAdvancedExtension` default conformances classes
- added `query.QueryConformanceClasses` Enum
- added `SortConformanceClasses` Enum

## [4.0.1] - 2025-01-23

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
from .aggregation import AggregationExtension
from .collection_search import CollectionSearchExtension, CollectionSearchPostExtension
from .fields import FieldsExtension
from .filter import FilterExtension
from .filter import (
CollectionSearchFilterExtension,
FilterExtension,
ItemCollectionFilterExtension,
SearchFilterExtension,
)
from .free_text import FreeTextAdvancedExtension, FreeTextExtension
from .pagination import (
OffsetPaginationExtension,
Expand All @@ -28,4 +33,7 @@
"TransactionExtension",
"CollectionSearchExtension",
"CollectionSearchPostExtension",
"SearchFilterExtension",
"ItemCollectionFilterExtension",
"CollectionSearchFilterExtension",
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Aggregation extension module."""

from .aggregation import AggregationExtension
from .aggregation import AggregationConformanceClasses, AggregationExtension

__all__ = ["AggregationExtension"]
__all__ = ["AggregationExtension", "AggregationConformanceClasses"]
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Collection-Search extension module."""

from .collection_search import (
CollectionSearchConformanceClasses,
CollectionSearchExtension,
CollectionSearchPostExtension,
ConformanceClasses,
)

__all__ = [
"CollectionSearchExtension",
"CollectionSearchPostExtension",
"ConformanceClasses",
"CollectionSearchConformanceClasses",
]
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .request import BaseCollectionSearchGetRequest, BaseCollectionSearchPostRequest


class ConformanceClasses(str, Enum):
class CollectionSearchConformanceClasses(str, Enum):
"""Conformance classes for the Collection-Search extension.
See
Expand All @@ -26,11 +26,6 @@ class ConformanceClasses(str, Enum):

COLLECTIONSEARCH = "https://api.stacspec.org/v1.0.0-rc.1/collection-search"
BASIS = "http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/simple-query"
FREETEXT = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#free-text"
FILTER = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter"
QUERY = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#query"
SORT = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#sort"
FIELDS = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#fields"


@attr.s
Expand All @@ -56,7 +51,10 @@ class CollectionSearchExtension(ApiExtension):
POST = None

conformance_classes: List[str] = attr.ib(
default=[ConformanceClasses.COLLECTIONSEARCH, ConformanceClasses.BASIS]
default=[
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
CollectionSearchConformanceClasses.BASIS,
]
)
schema_href: Optional[str] = attr.ib(default=None)

Expand All @@ -78,21 +76,13 @@ def from_extensions(
schema_href: Optional[str] = None,
) -> "CollectionSearchExtension":
"""Create CollectionSearchExtension object from extensions."""
known_extension_conformances = {
"FreeTextExtension": ConformanceClasses.FREETEXT,
"FreeTextAdvancedExtension": ConformanceClasses.FREETEXT,
"QueryExtension": ConformanceClasses.QUERY,
"SortExtension": ConformanceClasses.SORT,
"FieldsExtension": ConformanceClasses.FIELDS,
"FilterExtension": ConformanceClasses.FILTER,
}

conformance_classes = [
ConformanceClasses.COLLECTIONSEARCH,
ConformanceClasses.BASIS,
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
CollectionSearchConformanceClasses.BASIS,
]
for ext in extensions:
if conf := known_extension_conformances.get(ext.__class__.__name__, None):
conformance_classes.append(conf)
conformance_classes.extend(ext.conformance_classes)

get_request_model = create_request_model(
model_name="CollectionsGetRequest",
Expand Down Expand Up @@ -128,7 +118,10 @@ class CollectionSearchPostExtension(CollectionSearchExtension):
client: Union[AsyncBaseCollectionSearchClient, BaseCollectionSearchClient] = attr.ib()
settings: ApiSettings = attr.ib()
conformance_classes: List[str] = attr.ib(
default=[ConformanceClasses.COLLECTIONSEARCH, ConformanceClasses.BASIS]
default=[
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
CollectionSearchConformanceClasses.BASIS,
]
)
schema_href: Optional[str] = attr.ib(default=None)
router: APIRouter = attr.ib(factory=APIRouter)
Expand Down Expand Up @@ -180,21 +173,12 @@ def from_extensions(
router: Optional[APIRouter] = None,
) -> "CollectionSearchPostExtension":
"""Create CollectionSearchPostExtension object from extensions."""
known_extension_conformances = {
"FreeTextExtension": ConformanceClasses.FREETEXT,
"FreeTextAdvancedExtension": ConformanceClasses.FREETEXT,
"QueryExtension": ConformanceClasses.QUERY,
"SortExtension": ConformanceClasses.SORT,
"FieldsExtension": ConformanceClasses.FIELDS,
"FilterExtension": ConformanceClasses.FILTER,
}
conformance_classes = [
ConformanceClasses.COLLECTIONSEARCH,
ConformanceClasses.BASIS,
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
CollectionSearchConformanceClasses.BASIS,
]
for ext in extensions:
if conf := known_extension_conformances.get(ext.__class__.__name__, None):
conformance_classes.append(conf)
conformance_classes.extend(ext.conformance_classes)

get_request_model = create_request_model(
model_name="CollectionsGetRequest",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Fields extension module."""

from .fields import FieldsExtension
from .fields import FieldsConformanceClasses, FieldsExtension

__all__ = ["FieldsExtension"]
__all__ = ["FieldsExtension", "FieldsConformanceClasses"]
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Fields extension."""

from enum import Enum
from typing import List, Optional

import attr
Expand All @@ -10,6 +11,18 @@
from .request import FieldsExtensionGetRequest, FieldsExtensionPostRequest


class FieldsConformanceClasses(str, Enum):
"""Conformance classes for the Fields extension.
See https://github.com/stac-api-extensions/fields
"""

SEARCH = "https://api.stacspec.org/v1.0.0/item-search#fields"
ITEMS = "https://api.stacspec.org/v1.0.0/ogcapi-features#fields"
COLLECTIONS = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#fields"


@attr.s
class FieldsExtension(ApiExtension):
"""Fields Extension.
Expand All @@ -33,7 +46,9 @@ class FieldsExtension(ApiExtension):
POST = FieldsExtensionPostRequest

conformance_classes: List[str] = attr.ib(
factory=lambda: ["https://api.stacspec.org/v1.0.0/item-search#fields"]
factory=lambda: [
FieldsConformanceClasses.SEARCH,
]
)
schema_href: Optional[str] = attr.ib(default=None)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
"""Filter extension module."""

from .filter import FilterExtension
from .filter import (
CollectionSearchFilterExtension,
FilterConformanceClasses,
FilterExtension,
ItemCollectionFilterExtension,
SearchFilterExtension,
)

__all__ = ["FilterExtension"]
__all__ = [
"FilterConformanceClasses",
"FilterExtension",
"SearchFilterExtension",
"ItemCollectionFilterExtension",
"CollectionSearchFilterExtension",
]
122 changes: 116 additions & 6 deletions stac_fastapi/extensions/stac_fastapi/extensions/core/filter/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ class FilterConformanceClasses(str, Enum):
"""

FILTER = "http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/filter"
FEATURES_FILTER = (
"http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter"
)
ITEM_SEARCH_FILTER = "https://api.stacspec.org/v1.0.0-rc.2/item-search#filter"

SEARCH = "https://api.stacspec.org/v1.0.0-rc.2/item-search#filter"
ITEMS = "http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter"
COLLECTIONS = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter"

CQL2_TEXT = "http://www.opengis.net/spec/cql2/1.0/conf/cql2-text"
CQL2_JSON = "http://www.opengis.net/spec/cql2/1.0/conf/cql2-json"
BASIC_CQL2 = "http://www.opengis.net/spec/cql2/1.0/conf/basic-cql2"
Expand Down Expand Up @@ -73,8 +74,8 @@ class FilterExtension(ApiExtension):
conformance_classes: List[str] = attr.ib(
default=[
FilterConformanceClasses.FILTER,
FilterConformanceClasses.FEATURES_FILTER,
FilterConformanceClasses.ITEM_SEARCH_FILTER,
FilterConformanceClasses.SEARCH,
FilterConformanceClasses.ITEMS,
FilterConformanceClasses.BASIC_CQL2,
FilterConformanceClasses.CQL2_JSON,
FilterConformanceClasses.CQL2_TEXT,
Expand Down Expand Up @@ -124,3 +125,112 @@ def register(self, app: FastAPI) -> None:
endpoint=create_async_endpoint(self.client.get_queryables, CollectionUri),
)
app.include_router(self.router, tags=["Filter Extension"])


@attr.s
class SearchFilterExtension(FilterExtension):
"""Item Search Filter Extension."""

conformance_classes: List[str] = attr.ib(
default=[
FilterConformanceClasses.FILTER,
FilterConformanceClasses.SEARCH,
FilterConformanceClasses.BASIC_CQL2,
FilterConformanceClasses.CQL2_JSON,
FilterConformanceClasses.CQL2_TEXT,
]
)

def register(self, app: FastAPI) -> None:
"""Register the extension with a FastAPI application.
Args:
app: target FastAPI application.
Returns:
None
"""
self.router.prefix = app.state.router_prefix
self.router.add_api_route(
name="Queryables",
path="/queryables",
methods=["GET"],
responses={
200: {
"content": {
"application/schema+json": {},
},
# TODO: add output model in stac-pydantic
},
},
response_class=self.response_class,
endpoint=create_async_endpoint(self.client.get_queryables, EmptyRequest),
)
app.include_router(self.router, tags=["Filter Extension"])


@attr.s
class ItemCollectionFilterExtension(FilterExtension):
"""Item Collection Filter Extension."""

conformance_classes: List[str] = attr.ib(
default=[
FilterConformanceClasses.FILTER,
FilterConformanceClasses.ITEMS,
FilterConformanceClasses.BASIC_CQL2,
FilterConformanceClasses.CQL2_JSON,
FilterConformanceClasses.CQL2_TEXT,
]
)

def register(self, app: FastAPI) -> None:
"""Register the extension with a FastAPI application.
Args:
app: target FastAPI application.
Returns:
None
"""
self.router.add_api_route(
name="Collection Queryables",
path="/collections/{collection_id}/queryables",
methods=["GET"],
responses={
200: {
"content": {
"application/schema+json": {},
},
# TODO: add output model in stac-pydantic
},
},
response_class=self.response_class,
endpoint=create_async_endpoint(self.client.get_queryables, CollectionUri),
)
app.include_router(self.router, tags=["Filter Extension"])


@attr.s
class CollectionSearchFilterExtension(FilterExtension):
"""Collection Search Filter Extension."""

conformance_classes: List[str] = attr.ib(
default=[
FilterConformanceClasses.FILTER,
FilterConformanceClasses.COLLECTIONS,
FilterConformanceClasses.BASIC_CQL2,
FilterConformanceClasses.CQL2_JSON,
FilterConformanceClasses.CQL2_TEXT,
]
)

def register(self, app: FastAPI) -> None:
"""Register the extension with a FastAPI application.
Args:
app: target FastAPI application.
Returns:
None
"""
pass
Loading

0 comments on commit 2fbcfc0

Please sign in to comment.