Skip to content

Commit 2fbcfc0

Browse files
refactored conformance classes for extensions (#790)
* refactored conformance classes for extensions * update changelog
1 parent 62ba40c commit 2fbcfc0

File tree

17 files changed

+391
-120
lines changed

17 files changed

+391
-120
lines changed

CHANGES.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
## [Unreleased]
44

5+
### Changed
6+
7+
- refactored conformance classes for extensions
8+
9+
- renamed `collection_search.ConformanceClasses` -> `collection_search.CollectionSearchConformanceClasses`
10+
- removed `FREETEXT`, `FILTER`, `QUERY`, `SORT` and `FIELDS` entries from the `CollectionSearchConformanceClasses` Enum (and moved to each extension's Enum)
11+
- changed `collection_search.CollectionSearchPostExtension.from_extension(ext)` to use the conformance classes from the input extensions to derive the output conformance classes.
12+
- added `fields.FieldsConformanceClasses` Enum
13+
- renamed `filter.FilterConformanceClasses.FEATURES_FILTER` -> `filter.FilterConformanceClasses.ITEMS`
14+
- renamed `filter.FilterConformanceClasses.ITEM_SEARCH_FILTER` -> `filter.FilterConformanceClasses.SEARCH`
15+
- added `filter.FilterConformanceClasses.COLLECTIONS`
16+
- added `filter.SearchFilterExtension`, `filter.ItemCollectionFilterExtension` and `filter.CollectionSearchFilterExtension` endpoint specific extensions
17+
- removed `FreeTextConformanceClasses.COLLECTIONS` and `FreeTextConformanceClasses.ITEMS` in `FreeTextExtension` and `FreeTextAdvancedExtension` default conformances classes
18+
- added `query.QueryConformanceClasses` Enum
19+
- added `SortConformanceClasses` Enum
20+
521
## [4.0.1] - 2025-01-23
622

723
### Changed

stac_fastapi/extensions/stac_fastapi/extensions/core/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
from .aggregation import AggregationExtension
44
from .collection_search import CollectionSearchExtension, CollectionSearchPostExtension
55
from .fields import FieldsExtension
6-
from .filter import FilterExtension
6+
from .filter import (
7+
CollectionSearchFilterExtension,
8+
FilterExtension,
9+
ItemCollectionFilterExtension,
10+
SearchFilterExtension,
11+
)
712
from .free_text import FreeTextAdvancedExtension, FreeTextExtension
813
from .pagination import (
914
OffsetPaginationExtension,
@@ -28,4 +33,7 @@
2833
"TransactionExtension",
2934
"CollectionSearchExtension",
3035
"CollectionSearchPostExtension",
36+
"SearchFilterExtension",
37+
"ItemCollectionFilterExtension",
38+
"CollectionSearchFilterExtension",
3139
)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Aggregation extension module."""
22

3-
from .aggregation import AggregationExtension
3+
from .aggregation import AggregationConformanceClasses, AggregationExtension
44

5-
__all__ = ["AggregationExtension"]
5+
__all__ = ["AggregationExtension", "AggregationConformanceClasses"]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"""Collection-Search extension module."""
22

33
from .collection_search import (
4+
CollectionSearchConformanceClasses,
45
CollectionSearchExtension,
56
CollectionSearchPostExtension,
6-
ConformanceClasses,
77
)
88

99
__all__ = [
1010
"CollectionSearchExtension",
1111
"CollectionSearchPostExtension",
12-
"ConformanceClasses",
12+
"CollectionSearchConformanceClasses",
1313
]

stac_fastapi/extensions/stac_fastapi/extensions/core/collection_search/collection_search.py

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from .request import BaseCollectionSearchGetRequest, BaseCollectionSearchPostRequest
1818

1919

20-
class ConformanceClasses(str, Enum):
20+
class CollectionSearchConformanceClasses(str, Enum):
2121
"""Conformance classes for the Collection-Search extension.
2222
2323
See
@@ -26,11 +26,6 @@ class ConformanceClasses(str, Enum):
2626

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

3530

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

5853
conformance_classes: List[str] = attr.ib(
59-
default=[ConformanceClasses.COLLECTIONSEARCH, ConformanceClasses.BASIS]
54+
default=[
55+
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
56+
CollectionSearchConformanceClasses.BASIS,
57+
]
6058
)
6159
schema_href: Optional[str] = attr.ib(default=None)
6260

@@ -78,21 +76,13 @@ def from_extensions(
7876
schema_href: Optional[str] = None,
7977
) -> "CollectionSearchExtension":
8078
"""Create CollectionSearchExtension object from extensions."""
81-
known_extension_conformances = {
82-
"FreeTextExtension": ConformanceClasses.FREETEXT,
83-
"FreeTextAdvancedExtension": ConformanceClasses.FREETEXT,
84-
"QueryExtension": ConformanceClasses.QUERY,
85-
"SortExtension": ConformanceClasses.SORT,
86-
"FieldsExtension": ConformanceClasses.FIELDS,
87-
"FilterExtension": ConformanceClasses.FILTER,
88-
}
79+
8980
conformance_classes = [
90-
ConformanceClasses.COLLECTIONSEARCH,
91-
ConformanceClasses.BASIS,
81+
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
82+
CollectionSearchConformanceClasses.BASIS,
9283
]
9384
for ext in extensions:
94-
if conf := known_extension_conformances.get(ext.__class__.__name__, None):
95-
conformance_classes.append(conf)
85+
conformance_classes.extend(ext.conformance_classes)
9686

9787
get_request_model = create_request_model(
9888
model_name="CollectionsGetRequest",
@@ -128,7 +118,10 @@ class CollectionSearchPostExtension(CollectionSearchExtension):
128118
client: Union[AsyncBaseCollectionSearchClient, BaseCollectionSearchClient] = attr.ib()
129119
settings: ApiSettings = attr.ib()
130120
conformance_classes: List[str] = attr.ib(
131-
default=[ConformanceClasses.COLLECTIONSEARCH, ConformanceClasses.BASIS]
121+
default=[
122+
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
123+
CollectionSearchConformanceClasses.BASIS,
124+
]
132125
)
133126
schema_href: Optional[str] = attr.ib(default=None)
134127
router: APIRouter = attr.ib(factory=APIRouter)
@@ -180,21 +173,12 @@ def from_extensions(
180173
router: Optional[APIRouter] = None,
181174
) -> "CollectionSearchPostExtension":
182175
"""Create CollectionSearchPostExtension object from extensions."""
183-
known_extension_conformances = {
184-
"FreeTextExtension": ConformanceClasses.FREETEXT,
185-
"FreeTextAdvancedExtension": ConformanceClasses.FREETEXT,
186-
"QueryExtension": ConformanceClasses.QUERY,
187-
"SortExtension": ConformanceClasses.SORT,
188-
"FieldsExtension": ConformanceClasses.FIELDS,
189-
"FilterExtension": ConformanceClasses.FILTER,
190-
}
191176
conformance_classes = [
192-
ConformanceClasses.COLLECTIONSEARCH,
193-
ConformanceClasses.BASIS,
177+
CollectionSearchConformanceClasses.COLLECTIONSEARCH,
178+
CollectionSearchConformanceClasses.BASIS,
194179
]
195180
for ext in extensions:
196-
if conf := known_extension_conformances.get(ext.__class__.__name__, None):
197-
conformance_classes.append(conf)
181+
conformance_classes.extend(ext.conformance_classes)
198182

199183
get_request_model = create_request_model(
200184
model_name="CollectionsGetRequest",
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Fields extension module."""
22

3-
from .fields import FieldsExtension
3+
from .fields import FieldsConformanceClasses, FieldsExtension
44

5-
__all__ = ["FieldsExtension"]
5+
__all__ = ["FieldsExtension", "FieldsConformanceClasses"]

stac_fastapi/extensions/stac_fastapi/extensions/core/fields/fields.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Fields extension."""
22

3+
from enum import Enum
34
from typing import List, Optional
45

56
import attr
@@ -10,6 +11,18 @@
1011
from .request import FieldsExtensionGetRequest, FieldsExtensionPostRequest
1112

1213

14+
class FieldsConformanceClasses(str, Enum):
15+
"""Conformance classes for the Fields extension.
16+
17+
See https://github.com/stac-api-extensions/fields
18+
19+
"""
20+
21+
SEARCH = "https://api.stacspec.org/v1.0.0/item-search#fields"
22+
ITEMS = "https://api.stacspec.org/v1.0.0/ogcapi-features#fields"
23+
COLLECTIONS = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#fields"
24+
25+
1326
@attr.s
1427
class FieldsExtension(ApiExtension):
1528
"""Fields Extension.
@@ -33,7 +46,9 @@ class FieldsExtension(ApiExtension):
3346
POST = FieldsExtensionPostRequest
3447

3548
conformance_classes: List[str] = attr.ib(
36-
factory=lambda: ["https://api.stacspec.org/v1.0.0/item-search#fields"]
49+
factory=lambda: [
50+
FieldsConformanceClasses.SEARCH,
51+
]
3752
)
3853
schema_href: Optional[str] = attr.ib(default=None)
3954

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
"""Filter extension module."""
22

3-
from .filter import FilterExtension
3+
from .filter import (
4+
CollectionSearchFilterExtension,
5+
FilterConformanceClasses,
6+
FilterExtension,
7+
ItemCollectionFilterExtension,
8+
SearchFilterExtension,
9+
)
410

5-
__all__ = ["FilterExtension"]
11+
__all__ = [
12+
"FilterConformanceClasses",
13+
"FilterExtension",
14+
"SearchFilterExtension",
15+
"ItemCollectionFilterExtension",
16+
"CollectionSearchFilterExtension",
17+
]

stac_fastapi/extensions/stac_fastapi/extensions/core/filter/filter.py

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ class FilterConformanceClasses(str, Enum):
2323
"""
2424

2525
FILTER = "http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/filter"
26-
FEATURES_FILTER = (
27-
"http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter"
28-
)
29-
ITEM_SEARCH_FILTER = "https://api.stacspec.org/v1.0.0-rc.2/item-search#filter"
26+
27+
SEARCH = "https://api.stacspec.org/v1.0.0-rc.2/item-search#filter"
28+
ITEMS = "http://www.opengis.net/spec/ogcapi-features-3/1.0/conf/features-filter"
29+
COLLECTIONS = "https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter"
30+
3031
CQL2_TEXT = "http://www.opengis.net/spec/cql2/1.0/conf/cql2-text"
3132
CQL2_JSON = "http://www.opengis.net/spec/cql2/1.0/conf/cql2-json"
3233
BASIC_CQL2 = "http://www.opengis.net/spec/cql2/1.0/conf/basic-cql2"
@@ -73,8 +74,8 @@ class FilterExtension(ApiExtension):
7374
conformance_classes: List[str] = attr.ib(
7475
default=[
7576
FilterConformanceClasses.FILTER,
76-
FilterConformanceClasses.FEATURES_FILTER,
77-
FilterConformanceClasses.ITEM_SEARCH_FILTER,
77+
FilterConformanceClasses.SEARCH,
78+
FilterConformanceClasses.ITEMS,
7879
FilterConformanceClasses.BASIC_CQL2,
7980
FilterConformanceClasses.CQL2_JSON,
8081
FilterConformanceClasses.CQL2_TEXT,
@@ -124,3 +125,112 @@ def register(self, app: FastAPI) -> None:
124125
endpoint=create_async_endpoint(self.client.get_queryables, CollectionUri),
125126
)
126127
app.include_router(self.router, tags=["Filter Extension"])
128+
129+
130+
@attr.s
131+
class SearchFilterExtension(FilterExtension):
132+
"""Item Search Filter Extension."""
133+
134+
conformance_classes: List[str] = attr.ib(
135+
default=[
136+
FilterConformanceClasses.FILTER,
137+
FilterConformanceClasses.SEARCH,
138+
FilterConformanceClasses.BASIC_CQL2,
139+
FilterConformanceClasses.CQL2_JSON,
140+
FilterConformanceClasses.CQL2_TEXT,
141+
]
142+
)
143+
144+
def register(self, app: FastAPI) -> None:
145+
"""Register the extension with a FastAPI application.
146+
147+
Args:
148+
app: target FastAPI application.
149+
150+
Returns:
151+
None
152+
"""
153+
self.router.prefix = app.state.router_prefix
154+
self.router.add_api_route(
155+
name="Queryables",
156+
path="/queryables",
157+
methods=["GET"],
158+
responses={
159+
200: {
160+
"content": {
161+
"application/schema+json": {},
162+
},
163+
# TODO: add output model in stac-pydantic
164+
},
165+
},
166+
response_class=self.response_class,
167+
endpoint=create_async_endpoint(self.client.get_queryables, EmptyRequest),
168+
)
169+
app.include_router(self.router, tags=["Filter Extension"])
170+
171+
172+
@attr.s
173+
class ItemCollectionFilterExtension(FilterExtension):
174+
"""Item Collection Filter Extension."""
175+
176+
conformance_classes: List[str] = attr.ib(
177+
default=[
178+
FilterConformanceClasses.FILTER,
179+
FilterConformanceClasses.ITEMS,
180+
FilterConformanceClasses.BASIC_CQL2,
181+
FilterConformanceClasses.CQL2_JSON,
182+
FilterConformanceClasses.CQL2_TEXT,
183+
]
184+
)
185+
186+
def register(self, app: FastAPI) -> None:
187+
"""Register the extension with a FastAPI application.
188+
189+
Args:
190+
app: target FastAPI application.
191+
192+
Returns:
193+
None
194+
"""
195+
self.router.add_api_route(
196+
name="Collection Queryables",
197+
path="/collections/{collection_id}/queryables",
198+
methods=["GET"],
199+
responses={
200+
200: {
201+
"content": {
202+
"application/schema+json": {},
203+
},
204+
# TODO: add output model in stac-pydantic
205+
},
206+
},
207+
response_class=self.response_class,
208+
endpoint=create_async_endpoint(self.client.get_queryables, CollectionUri),
209+
)
210+
app.include_router(self.router, tags=["Filter Extension"])
211+
212+
213+
@attr.s
214+
class CollectionSearchFilterExtension(FilterExtension):
215+
"""Collection Search Filter Extension."""
216+
217+
conformance_classes: List[str] = attr.ib(
218+
default=[
219+
FilterConformanceClasses.FILTER,
220+
FilterConformanceClasses.COLLECTIONS,
221+
FilterConformanceClasses.BASIC_CQL2,
222+
FilterConformanceClasses.CQL2_JSON,
223+
FilterConformanceClasses.CQL2_TEXT,
224+
]
225+
)
226+
227+
def register(self, app: FastAPI) -> None:
228+
"""Register the extension with a FastAPI application.
229+
230+
Args:
231+
app: target FastAPI application.
232+
233+
Returns:
234+
None
235+
"""
236+
pass

0 commit comments

Comments
 (0)