Skip to content

Collection search get request #407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Added support for Collections search

## [v6.0.0] - 2025-06-22

### Added
Expand Down
3 changes: 1 addition & 2 deletions dockerfiles/Dockerfile.dev.os
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ FROM python:3.10-slim
# update apt pkgs, and install build-essential for ciso8601
RUN apt-get update && \
apt-get -y upgrade && \
apt-get -y install build-essential && \
apt-get -y install build-essential git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

RUN apt-get -y install git
# update certs used by Requests
ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt

Expand Down
29 changes: 28 additions & 1 deletion stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@
)
from stac_fastapi.extensions.core import (
AggregationExtension,
CollectionSearchExtension,
CollectionSearchFilterExtension,
FilterExtension,
FreeTextExtension,
SortExtension,
TokenPaginationExtension,
TransactionExtension,
)
from stac_fastapi.extensions.core.fields import FieldsConformanceClasses
from stac_fastapi.extensions.core.filter import FilterConformanceClasses
from stac_fastapi.extensions.core.free_text import FreeTextConformanceClasses
from stac_fastapi.extensions.core.query import QueryConformanceClasses
from stac_fastapi.extensions.core.sort import SortConformanceClasses
from stac_fastapi.extensions.third_party import BulkTransactionExtension
from stac_fastapi.sfeos_helpers.aggregation import EsAsyncBaseAggregationClient
from stac_fastapi.sfeos_helpers.filter import EsAsyncBaseFiltersClient
Expand All @@ -59,7 +65,6 @@
filter_extension.conformance_classes.append(
FilterConformanceClasses.ADVANCED_COMPARISON_OPERATORS
)

aggregation_extension = AggregationExtension(
client=EsAsyncBaseAggregationClient(
database=database_logic, session=session, settings=settings
Expand All @@ -68,6 +73,7 @@
aggregation_extension.POST = EsAggregationExtensionPostRequest
aggregation_extension.GET = EsAggregationExtensionGetRequest

# Base search extensions (without CollectionSearchExtension to avoid duplicates)
search_extensions = [
FieldsExtension(),
QueryExtension(),
Expand Down Expand Up @@ -98,8 +104,29 @@
),
)

# Initialize extensions with just the search and aggregation extensions
# Initialize with base extensions
extensions = [aggregation_extension] + search_extensions

# Create collection search extensions
collection_search_extensions = [
QueryExtension(conformance_classes=[QueryConformanceClasses.COLLECTIONS]),
SortExtension(conformance_classes=[SortConformanceClasses.COLLECTIONS]),
FieldsExtension(conformance_classes=[FieldsConformanceClasses.COLLECTIONS]),
CollectionSearchFilterExtension(
conformance_classes=[FilterConformanceClasses.COLLECTIONS]
),
FreeTextExtension(conformance_classes=[FreeTextConformanceClasses.COLLECTIONS]),
]

# Initialize collection search with its extensions
collection_search_ext = CollectionSearchExtension.from_extensions(
collection_search_extensions
)
collections_get_request_model = collection_search_ext.GET

extensions.append(collection_search_ext)

database_logic.extensions = [type(ext).__name__ for ext in extensions]

post_request_model = create_post_request_model(search_extensions)
Expand Down
30 changes: 28 additions & 2 deletions stac_fastapi/opensearch/stac_fastapi/opensearch/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,19 @@
from stac_fastapi.core.utilities import get_bool_env
from stac_fastapi.extensions.core import (
AggregationExtension,
CollectionSearchExtension,
CollectionSearchFilterExtension,
FilterExtension,
FreeTextExtension,
SortExtension,
TokenPaginationExtension,
TransactionExtension,
)
from stac_fastapi.extensions.core.fields import FieldsConformanceClasses
from stac_fastapi.extensions.core.filter import FilterConformanceClasses
from stac_fastapi.extensions.core.free_text import FreeTextConformanceClasses
from stac_fastapi.extensions.core.query import QueryConformanceClasses
from stac_fastapi.extensions.core.sort import SortConformanceClasses
from stac_fastapi.extensions.third_party import BulkTransactionExtension
from stac_fastapi.opensearch.config import OpensearchSettings
from stac_fastapi.opensearch.database_logic import (
Expand Down Expand Up @@ -59,7 +65,6 @@
filter_extension.conformance_classes.append(
FilterConformanceClasses.ADVANCED_COMPARISON_OPERATORS
)

aggregation_extension = AggregationExtension(
client=EsAsyncBaseAggregationClient(
database=database_logic, session=session, settings=settings
Expand All @@ -68,6 +73,7 @@
aggregation_extension.POST = EsAggregationExtensionPostRequest
aggregation_extension.GET = EsAggregationExtensionGetRequest

# Base search extensions (without CollectionSearchExtension to avoid duplicates)
search_extensions = [
FieldsExtension(),
QueryExtension(),
Expand All @@ -77,7 +83,6 @@
FreeTextExtension(),
]


if TRANSACTIONS_EXTENSIONS:
search_extensions.insert(
0,
Expand All @@ -99,8 +104,29 @@
),
)

# Initialize extensions with just the search and aggregation extensions
# Initialize with base extensions
extensions = [aggregation_extension] + search_extensions

# Create collection search extensions
collection_search_extensions = [
QueryExtension(conformance_classes=[QueryConformanceClasses.COLLECTIONS]),
SortExtension(conformance_classes=[SortConformanceClasses.COLLECTIONS]),
FieldsExtension(conformance_classes=[FieldsConformanceClasses.COLLECTIONS]),
CollectionSearchFilterExtension(
conformance_classes=[FilterConformanceClasses.COLLECTIONS]
),
FreeTextExtension(conformance_classes=[FreeTextConformanceClasses.COLLECTIONS]),
]

# Initialize collection search with its extensions
collection_search_ext = CollectionSearchExtension.from_extensions(
collection_search_extensions
)
collections_get_request_model = collection_search_ext.GET

extensions.append(collection_search_ext)

database_logic.extensions = [type(ext).__name__ for ext in extensions]

post_request_model = create_post_request_model(search_extensions)
Expand Down
13 changes: 11 additions & 2 deletions stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,17 @@ class Geometry(Protocol): # noqa
"dynamic_templates": ES_MAPPINGS_DYNAMIC_TEMPLATES,
"properties": {
"id": {"type": "keyword"},
"extent.spatial.bbox": {"type": "long"},
"extent.temporal.interval": {"type": "date"},
"bbox_shape": {"type": "geo_shape"}, # Only this is used for spatial queries
"extent": {
"properties": {"temporal": {"properties": {"interval": {"type": "date"}}}}
},
"properties": {
"properties": {
"datetime": {"type": "date"},
"start_datetime": {"type": "date"},
"end_datetime": {"type": "date"},
}
},
"providers": {"type": "object", "enabled": False},
"links": {"type": "object", "enabled": False},
"item_assets": {"type": "object", "enabled": False},
Expand Down
76 changes: 24 additions & 52 deletions stac_fastapi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,9 @@
CoreClient,
TransactionsClient,
)
from stac_fastapi.core.extensions import QueryExtension
from stac_fastapi.core.extensions.aggregation import (
EsAggregationExtensionGetRequest,
EsAggregationExtensionPostRequest,
)
from stac_fastapi.core.rate_limit import setup_rate_limit
from stac_fastapi.core.utilities import get_bool_env
from stac_fastapi.sfeos_helpers.aggregation import EsAsyncBaseAggregationClient
from stac_fastapi.extensions.third_party import BulkTransactionExtension

if os.getenv("BACKEND", "elasticsearch").lower() == "opensearch":
from stac_fastapi.opensearch.app import app_config
Expand All @@ -50,15 +45,7 @@
create_index_templates,
)

from stac_fastapi.extensions.core import (
AggregationExtension,
FieldsExtension,
FilterExtension,
FreeTextExtension,
SortExtension,
TokenPaginationExtension,
TransactionExtension,
)
from stac_fastapi.extensions.core import TransactionExtension
from stac_fastapi.types.config import Settings

DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
Expand Down Expand Up @@ -340,56 +327,41 @@ async def route_dependencies_client(route_dependencies_app):

def build_test_app():
"""Build a test app with configurable transaction extensions."""
# Create a copy of the base config
# Create a copy of the base config which already has all extensions configured
test_config = app_config.copy()

# Get transaction extensions setting
TRANSACTIONS_EXTENSIONS = get_bool_env(
"ENABLE_TRANSACTIONS_EXTENSIONS", default=True
)

# Configure extensions
settings = AsyncSettings()
aggregation_extension = AggregationExtension(
client=EsAsyncBaseAggregationClient(
database=database, session=None, settings=settings
)
)
aggregation_extension.POST = EsAggregationExtensionPostRequest
aggregation_extension.GET = EsAggregationExtensionGetRequest

search_extensions = [
SortExtension(),
FieldsExtension(),
QueryExtension(),
TokenPaginationExtension(),
FilterExtension(),
FreeTextExtension(),
]
# First remove any existing transaction extensions
if "extensions" in test_config:
test_config["extensions"] = [
ext
for ext in test_config["extensions"]
if not isinstance(ext, (TransactionExtension, BulkTransactionExtension))
]

# Add transaction extension if enabled
if TRANSACTIONS_EXTENSIONS:
search_extensions.append(
TransactionExtension(
client=TransactionsClient(
database=database, session=None, settings=settings
settings = AsyncSettings()
test_config["extensions"].extend(
[
TransactionExtension(
client=TransactionsClient(
database=database, session=None, settings=settings
),
settings=settings,
),
BulkTransactionExtension(
client=BulkTransactionsClient(
database=database, session=None, settings=settings
)
),
settings=settings,
)
]
)

# Update extensions in config
extensions = [aggregation_extension] + search_extensions
test_config["extensions"] = extensions

# Update client with new extensions
test_config["client"] = CoreClient(
database=database,
session=None,
extensions=extensions,
post_request_model=test_config["search_post_request_model"],
)

# Create and return the app
api = StacApi(**test_config)
return api.app