Skip to content

Commit 7b238f2

Browse files
authored
Merge branch 'main' into CAT-1382-2
2 parents 18339d6 + b0e39d6 commit 7b238f2

File tree

3 files changed

+133
-14
lines changed

3 files changed

+133
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Added
1111

12-
- CloudFerro logo to sponsors and supporters list [#485](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/485)
13-
- Latest news section to README [#485](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/485)
1412
- Environment variable `EXCLUDED_FROM_QUERYABLES` to exclude specific fields from queryables endpoint and filtering. Supports comma-separated list of fully qualified field names (e.g., `properties.auth:schemes,properties.storage:schemes`) [#489](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/489)
1513

1614
### Changed
@@ -21,14 +19,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2119

2220
### Updated
2321

22+
- Improved OpenAPI docs for `/collections-search` GET and POST endpoints. [#508](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/508)
2423

2524
## [v6.6.0] - 2025-10-21
2625

2726
### Added
2827

2928
- Spatial search support for collections via `bbox` parameter on `/collections` endpoint. Collections are now indexed with a `bbox_shape` field (GeoJSON polygon) derived from their spatial extent for efficient geospatial queries when created or updated. [#481](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/481
3029
- Introduced SFEOS Tools (`sfeos_tools/`) - An installable Click-based CLI package for managing SFEOS deployments. Initial command `add-bbox-shape` adds the `bbox_shape` field to existing collections for spatial search compatibility. Install with `pip install sfeos-tools[elasticsearch]` or `pip install sfeos-tools[opensearch]`. [#481](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/481)
31-
- Moved SFEOS Tools to its own repository at [Healy-Hyperspatial/sfeos-tools](https://github.com/Healy-Hyperspatial/sfeos-tools). The CLI package is now maintained separately. [#PR_NUMBER]
30+
- Moved SFEOS Tools to its own repository at [Healy-Hyperspatial/sfeos-tools](https://github.com/Healy-Hyperspatial/sfeos-tools). The CLI package is now maintained separately.
3231
- CloudFerro logo to sponsors and supporters list [#485](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/485)
3332
- Latest news section to README [#485](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/485)
3433
- Added Redis caching configuration for navigation pagination support, enabling proper `prev` and `next` links in paginated responses. [#488](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/488)

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,16 @@ The following organizations have contributed time and/or funding to support the
3030

3131
## Latest News
3232

33+
- **10/23/2025:** The `EXCLUDED_FROM_QUERYABLES` environment variable was added to exclude fields from the `queryables` endpoint. See [docs](#excluding-fields-from-queryables).
3334
- **10/15/2025:** 🚀 SFEOS Tools v0.1.0 Released! - The new `sfeos-tools` CLI is now available on [PyPI](https://pypi.org/project/sfeos-tools/)
3435
- **10/15/2025:** Added `reindex` command to **[SFEOS-tools](https://github.com/Healy-Hyperspatial/sfeos-tools)** for zero-downtime index updates when changing mappings or settings. The new `reindex` command makes it easy to apply mapping changes, update index settings, or migrate to new index structures without any service interruption, ensuring high availability of your STAC API during maintenance operations.
3536
- **10/12/2025:** Collections search **bbox** functionality added! The collections search extension now supports bbox queries. Collections will need to be updated via the API or with the new **[SFEOS-tools](https://github.com/Healy-Hyperspatial/sfeos-tools)** CLI package to support geospatial discoverability. 🙏 Thanks again to **CloudFerro** for their sponsorship of this work!
36-
- **10/04/2025:** The **[CloudFerro](https://cloudferro.com/)** logo has been added to the sponsors and supporters list above. Their sponsorship of the ongoing collections search extension work has been invaluable. This is in addition to the many other important changes and updates their developers have added to the project.
3737

3838
<details style="border: 1px solid #eaecef; border-radius: 6px; padding: 10px; margin-bottom: 16px; background-color: #f9f9f9;">
3939
<summary style="cursor: pointer; font-weight: bold; margin: -10px -10px 0; padding: 10px; background-color: #f0f0f0; border-bottom: 1px solid #eaecef; border-top-left-radius: 6px; border-top-right-radius: 6px;">View Older News (Click to Expand)</summary>
4040

4141
-------------
42+
- **10/04/2025:** The **[CloudFerro](https://cloudferro.com/)** logo has been added to the sponsors and supporters list above. Their sponsorship of the ongoing collections search extension work has been invaluable. This is in addition to the many other important changes and updates their developers have added to the project.
4243
- **09/25/2025:** v6.5.0 adds a new GET/POST /collections-search endpoint (disabled by default via ENABLE_COLLECTIONS_SEARCH_ROUTE) to avoid conflicts with the Transactions Extension, and enhances collections search with structured filtering (CQL2 JSON/text), query, and datetime filtering. These changes make collection discovery more powerful and configurable while preserving compatibility with transaction-enabled deployments.
4344
<!-- Add more older news items here in Markdown format; GitHub will parse them thanks to the blank line implicit in this structure -->
4445

stac_fastapi/core/stac_fastapi/core/extensions/collections_search.py

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""Collections search extension."""
22

3-
from typing import List, Optional, Type, Union
3+
from typing import Any, Dict, List, Optional, Type, Union
44

5-
from fastapi import APIRouter, FastAPI, Request
5+
from fastapi import APIRouter, Body, FastAPI, Query, Request
66
from fastapi.responses import JSONResponse
77
from pydantic import BaseModel
88
from stac_pydantic.api.search import ExtendedSearch
@@ -24,6 +24,105 @@ class CollectionsSearchRequest(ExtendedSearch):
2424
] = None # Legacy query extension (deprecated but still supported)
2525

2626

27+
def build_get_collections_search_doc(original_endpoint):
28+
"""Return a documented GET endpoint wrapper for /collections-search."""
29+
30+
async def documented_endpoint(
31+
request: Request,
32+
q: Optional[str] = Query(
33+
None,
34+
description="Free text search query",
35+
),
36+
query: Optional[str] = Query(
37+
None,
38+
description="Additional filtering expressed as a string (legacy support)",
39+
example="platform=landsat AND collection_category=level2",
40+
),
41+
limit: int = Query(
42+
10,
43+
ge=1,
44+
description=(
45+
"The maximum number of collections to return (page size). Defaults to 10."
46+
),
47+
),
48+
token: Optional[str] = Query(
49+
None,
50+
description="Pagination token for the next page of results",
51+
),
52+
bbox: Optional[str] = Query(
53+
None,
54+
description=(
55+
"Bounding box for spatial filtering in format 'minx,miny,maxx,maxy' "
56+
"or 'minx,miny,minz,maxx,maxy,maxz'"
57+
),
58+
),
59+
datetime: Optional[str] = Query(
60+
None,
61+
description=(
62+
"Temporal filter in ISO 8601 format (e.g., "
63+
"'2020-01-01T00:00:00Z/2021-01-01T00:00:00Z')"
64+
),
65+
),
66+
sortby: Optional[str] = Query(
67+
None,
68+
description=(
69+
"Sorting criteria in the format 'field' or '-field' for descending order"
70+
),
71+
),
72+
fields: Optional[List[str]] = Query(
73+
None,
74+
description=(
75+
"Comma-separated list of fields to include or exclude (use -field to exclude)"
76+
),
77+
alias="fields[]",
78+
),
79+
):
80+
# Delegate to original endpoint which reads from request.query_params
81+
return await original_endpoint(request)
82+
83+
documented_endpoint.__name__ = original_endpoint.__name__
84+
return documented_endpoint
85+
86+
87+
def build_post_collections_search_doc(original_post_endpoint):
88+
"""Return a documented POST endpoint wrapper for /collections-search."""
89+
90+
async def documented_post_endpoint(
91+
request: Request,
92+
body: Dict[str, Any] = Body(
93+
...,
94+
description=(
95+
"Search parameters for collections.\n\n"
96+
"- `q`: Free text search query (string or list of strings)\n"
97+
"- `query`: Additional filtering expressed as a string (legacy support)\n"
98+
"- `limit`: Maximum number of results to return (default: 10)\n"
99+
"- `token`: Pagination token for the next page of results\n"
100+
"- `bbox`: Bounding box [minx, miny, maxx, maxy] or [minx, miny, minz, maxx, maxy, maxz]\n"
101+
"- `datetime`: Temporal filter in ISO 8601 (e.g., '2020-01-01T00:00:00Z/2021-01-01T12:31:12Z')\n"
102+
"- `sortby`: List of sort criteria objects with 'field' and 'direction' (asc/desc)\n"
103+
"- `fields`: Object with 'include' and 'exclude' arrays for field selection"
104+
),
105+
example={
106+
"q": "landsat",
107+
"query": "platform=landsat AND collection_category=level2",
108+
"limit": 10,
109+
"token": "next-page-token",
110+
"bbox": [-180, -90, 180, 90],
111+
"datetime": "2020-01-01T00:00:00Z/2021-01-01T12:31:12Z",
112+
"sortby": [{"field": "id", "direction": "asc"}],
113+
"fields": {
114+
"include": ["id", "title", "description"],
115+
"exclude": ["properties"],
116+
},
117+
},
118+
),
119+
) -> Union[Collections, Response]:
120+
return await original_post_endpoint(request, body)
121+
122+
documented_post_endpoint.__name__ = original_post_endpoint.__name__
123+
return documented_post_endpoint
124+
125+
27126
class CollectionsSearchEndpointExtension(ApiExtension):
28127
"""Collections search endpoint extension.
29128
@@ -54,7 +153,6 @@ def __init__(
54153
self.POST = POST
55154
self.conformance_classes = conformance_classes or []
56155
self.router = APIRouter()
57-
self.create_endpoints()
58156

59157
def register(self, app: FastAPI) -> None:
60158
"""Register the extension with a FastAPI application.
@@ -65,32 +163,53 @@ def register(self, app: FastAPI) -> None:
65163
Returns:
66164
None
67165
"""
68-
app.include_router(self.router)
166+
# Remove any existing routes to avoid duplicates
167+
self.router.routes = []
69168

70-
def create_endpoints(self) -> None:
71-
"""Create endpoints for the extension."""
169+
# Recreate endpoints with proper OpenAPI documentation
72170
if self.GET:
171+
original_endpoint = self.collections_search_get_endpoint
172+
documented_endpoint = build_get_collections_search_doc(original_endpoint)
173+
73174
self.router.add_api_route(
74-
name="Get Collections Search",
75175
path="/collections-search",
176+
endpoint=documented_endpoint,
76177
response_model=None,
77178
response_class=JSONResponse,
78179
methods=["GET"],
79-
endpoint=self.collections_search_get_endpoint,
180+
summary="Search collections",
181+
description=(
182+
"Search for collections using query parameters. "
183+
"Supports filtering, sorting, and field selection."
184+
),
185+
response_description="A list of collections matching the search criteria",
186+
tags=["Collections Search Extension"],
80187
**(self.settings if isinstance(self.settings, dict) else {}),
81188
)
82189

83190
if self.POST:
191+
original_post_endpoint = self.collections_search_post_endpoint
192+
documented_post_endpoint = build_post_collections_search_doc(
193+
original_post_endpoint
194+
)
195+
84196
self.router.add_api_route(
85-
name="Post Collections Search",
86197
path="/collections-search",
198+
endpoint=documented_post_endpoint,
87199
response_model=None,
88200
response_class=JSONResponse,
89201
methods=["POST"],
90-
endpoint=self.collections_search_post_endpoint,
202+
summary="Search collections",
203+
description=(
204+
"Search for collections using a JSON request body. "
205+
"Supports filtering, sorting, field selection, and pagination."
206+
),
207+
tags=["Collections Search Extension"],
91208
**(self.settings if isinstance(self.settings, dict) else {}),
92209
)
93210

211+
app.include_router(self.router)
212+
94213
async def collections_search_get_endpoint(
95214
self, request: Request
96215
) -> Union[Collections, Response]:

0 commit comments

Comments
 (0)