Skip to content
Merged
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
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,28 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

### Changed

### Fixed

### Removed

### Updated

## [v6.7.0] - 2025-10-27

### Added

- 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)
- 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)

### Changed

### Fixed

- Fixed filter parameter handling for GET `/collections-search` endpoint. Filter parameters (`filter` and `filter-lang`) are now properly passed through and processed. [#511](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/511)
- Fixed `q` parameter in GET `/collections-search` endpoint to be converted to a list format, matching the behavior of the `/collections` endpoint for consistency. [#511](https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/pull/511)

### Removed

### Updated
Expand Down Expand Up @@ -594,7 +609,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Use genexp in execute_search and get_all_collections to return results.
- Added db_to_stac serializer to item_collection method in core.py.

[Unreleased]: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/compare/v6.6.0...main
[Unreleased]: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/compare/v6.7.0...main
[v6.7.0]: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/compare/v6.6.0...v6.7.0
[v6.6.0]: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/compare/v6.5.1...v6.6.0
[v6.5.1]: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/compare/v6.5.0...v6.5.1
[v6.5.0]: https://github.com/stac-utils/stac-fastapi-elasticsearch-opensearch/compare/v6.4.0...v6.5.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ class CollectionsSearchRequest(ExtendedSearch):
query: Optional[
str
] = None # Legacy query extension (deprecated but still supported)
filter_expr: Optional[str] = None
filter_lang: Optional[str] = None


def build_get_collections_search_doc(original_endpoint):
"""Return a documented GET endpoint wrapper for /collections-search."""

async def documented_endpoint(
request: Request,
q: Optional[str] = Query(
q: Optional[Union[str, List[str]]] = Query(
None,
description="Free text search query",
),
Expand Down Expand Up @@ -76,9 +78,63 @@ async def documented_endpoint(
),
alias="fields[]",
),
filter: Optional[str] = Query(
None,
description=(
"Structured filter expression in CQL2 JSON or CQL2-text format"
),
example='{"op": "=", "args": [{"property": "properties.category"}, "level2"]}',
),
filter_lang: Optional[str] = Query(
None,
description=(
"Filter language. Must be 'cql2-json' or 'cql2-text' if specified"
),
example="cql2-json",
),
):
# Delegate to original endpoint which reads from request.query_params
return await original_endpoint(request)
# Delegate to original endpoint with parameters
# Since FastAPI extracts parameters from the URL when they're defined as function parameters,
# we need to create a request wrapper that provides our modified query_params

# Create a mutable copy of query_params
if hasattr(request, "_query_params"):
query_params = dict(request._query_params)
else:
query_params = dict(request.query_params)

# Add q parameter back to query_params if it was provided
# Convert to list format to match /collections behavior
if q is not None:
if isinstance(q, str):
# Single string should become a list with one element
query_params["q"] = [q]
elif isinstance(q, list):
# Already a list, use as-is
query_params["q"] = q

# Add filter parameters back to query_params if they were provided
if filter is not None:
query_params["filter"] = filter
if filter_lang is not None:
query_params["filter-lang"] = filter_lang

# Create a request wrapper that provides our modified query_params
class RequestWrapper:
def __init__(self, original_request, modified_query_params):
self._original = original_request
self._query_params = modified_query_params

@property
def query_params(self):
return self._query_params

def __getattr__(self, name):
# Delegate all other attributes to the original request
return getattr(self._original, name)

wrapped_request = RequestWrapper(request, query_params)
return await original_endpoint(wrapped_request)

documented_endpoint.__name__ = original_endpoint.__name__
return documented_endpoint
Expand All @@ -95,6 +151,8 @@ async def documented_post_endpoint(
"Search parameters for collections.\n\n"
"- `q`: Free text search query (string or list of strings)\n"
"- `query`: Additional filtering expressed as a string (legacy support)\n"
"- `filter`: Structured filter expression in CQL2 JSON or CQL2-text format\n"
"- `filter_lang`: Filter language. Must be 'cql2-json' or 'cql2-text' if specified\n"
"- `limit`: Maximum number of results to return (default: 10)\n"
"- `token`: Pagination token for the next page of results\n"
"- `bbox`: Bounding box [minx, miny, maxx, maxy] or [minx, miny, minz, maxx, maxy, maxz]\n"
Expand All @@ -105,6 +163,11 @@ async def documented_post_endpoint(
example={
"q": "landsat",
"query": "platform=landsat AND collection_category=level2",
"filter": {
"op": "=",
"args": [{"property": "properties.category"}, "level2"],
},
"filter_lang": "cql2-json",
"limit": 10,
"token": "next-page-token",
"bbox": [-180, -90, 180, 90],
Expand Down Expand Up @@ -243,6 +306,14 @@ async def collections_search_get_endpoint(
sortby = sortby_str.split(",")
params["sortby"] = sortby

# Handle filter parameter mapping (fixed for collections-search)
if "filter" in params:
params["filter_expr"] = params.pop("filter")

# Handle filter-lang parameter mapping (fixed for collections-search)
if "filter-lang" in params:
params["filter_lang"] = params.pop("filter-lang")

collections = await self.client.all_collections(request=request, **params)
return collections

Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi/core/stac_fastapi/core/version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"""library version."""
__version__ = "6.6.0"
__version__ = "6.7.0"
4 changes: 2 additions & 2 deletions stac_fastapi/elasticsearch/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ keywords = [
]
dynamic = ["version"]
dependencies = [
"stac-fastapi-core==6.6.0",
"sfeos-helpers==6.6.0",
"stac-fastapi-core==6.7.0",
"sfeos-helpers==6.7.0",
"elasticsearch[async]~=8.19.1",
"uvicorn~=0.23.0",
"starlette>=0.35.0,<0.36.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
FieldsConformanceClasses.COLLECTIONS,
],
)
extensions.append(collection_search_ext)
extensions.append(collections_search_endpoint_ext)


Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"""library version."""
__version__ = "6.6.0"
__version__ = "6.7.0"
4 changes: 2 additions & 2 deletions stac_fastapi/opensearch/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ keywords = [
]
dynamic = ["version"]
dependencies = [
"stac-fastapi-core==6.6.0",
"sfeos-helpers==6.6.0",
"stac-fastapi-core==6.7.0",
"sfeos-helpers==6.7.0",
"opensearch-py~=2.8.0",
"opensearch-py[async]~=2.8.0",
"uvicorn~=0.23.0",
Expand Down
2 changes: 1 addition & 1 deletion stac_fastapi/opensearch/stac_fastapi/opensearch/version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"""library version."""
__version__ = "6.6.0"
__version__ = "6.7.0"
2 changes: 1 addition & 1 deletion stac_fastapi/sfeos_helpers/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ keywords = [
]
dynamic = ["version"]
dependencies = [
"stac-fastapi.core==6.6.0",
"stac-fastapi.core==6.7.0",
]

[project.urls]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"""library version."""
__version__ = "6.6.0"
__version__ = "6.7.0"
5 changes: 2 additions & 3 deletions stac_fastapi/tests/api/test_api_search_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ async def test_collections_post(app_client, txn_client, ctx):
async def test_collections_search_cql2_text(app_client, txn_client, ctx):
"""Test collections search with CQL2-text filter."""
# Create a unique prefix for test collections
test_prefix = f"test-{uuid.uuid4()}"
test_prefix = f"test-{uuid.uuid4().hex[:8]}"

# Create test collections
collection_data = ctx.collection.copy()
Expand All @@ -855,9 +855,8 @@ async def test_collections_search_cql2_text(app_client, txn_client, ctx):
assert filtered_collections[0]["id"] == collection_id

# Test GET search with more complex CQL2-text filter (LIKE operator)
test_prefix_escaped = test_prefix.replace("-", "\\-")
resp = await app_client.get(
f"/collections-search?filter-lang=cql2-text&filter=id LIKE '{test_prefix_escaped}%'"
f"/collections-search?filter-lang=cql2-text&filter=id LIKE '{test_prefix}%'"
)
assert resp.status_code == 200
resp_json = resp.json()
Expand Down