Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 13 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

## [Unreleased]

- refactored conformance classes for extensions

- renamed `collection_search.ConformanceClasses` -> `collection_search.CollectionSearchConformanceClasses`
- `collection_search.CollectionSearchPostExtension.from_extension(ext)` method will now use the conformance classes from the input extensions to derived 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"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those have been moved to each extensions Enums (will update the changelog to mention it)



@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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we now extend the conformance classes with the classes found within the extensions


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"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, most extension will have search, items and collections


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"])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the search extension only register the /queryables endpoint



@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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe worth for add a note about why we're not registering default endpoint here

ref: stac-utils/stac-fastapi-pgstac#192 (comment)

Loading
Loading