Skip to content

Commit 3db9018

Browse files
committed
post fields
1 parent 0ff6cc0 commit 3db9018

File tree

5 files changed

+204
-2
lines changed

5 files changed

+204
-2
lines changed

stac_fastapi/core/stac_fastapi/core/core.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ async def all_collections(
255255
request = kwargs["request"]
256256
base_url = str(request.base_url)
257257

258+
print("fields get: ", fields)
259+
258260
limit = int(request.query_params.get("limit", os.getenv("STAC_ITEM_LIMIT", 10)))
259261

260262
token = request.query_params.get("token")
@@ -263,10 +265,18 @@ async def all_collections(
263265
includes, excludes = set(), set()
264266
if fields:
265267
for field in fields:
268+
print("Processing field:", field)
266269
if field[0] == "-":
267270
excludes.add(field[1:])
271+
print("Added to excludes:", field[1:])
268272
else:
269273
includes.add(field[1:] if field[0] in "+ " else field)
274+
print(
275+
"Added to includes:", field[1:] if field[0] in "+ " else field
276+
)
277+
print("Final includes:", includes)
278+
print("Final excludes:", excludes)
279+
print("fields get: ", fields)
270280

271281
sort = None
272282
if sortby:
@@ -391,6 +401,71 @@ async def all_collections(
391401
numberReturned=len(filtered_collections),
392402
)
393403

404+
async def post_all_collections(
405+
self, search_request: BaseSearchPostRequest, request: Request, **kwargs
406+
) -> stac_types.Collections:
407+
"""Search collections with POST request.
408+
409+
Args:
410+
search_request (BaseSearchPostRequest): The search request.
411+
request (Request): The request.
412+
413+
Returns:
414+
A Collections object containing all the collections in the database and links to various resources.
415+
"""
416+
# Convert fields parameter from POST format to all_collections format
417+
fields = None
418+
419+
if hasattr(search_request, "fields") and search_request.fields:
420+
fields = []
421+
422+
# Handle include fields
423+
if (
424+
hasattr(search_request.fields, "include")
425+
and search_request.fields.include
426+
):
427+
for field in search_request.fields.include:
428+
fields.append(f"+{field}")
429+
430+
# Handle exclude fields
431+
if (
432+
hasattr(search_request.fields, "exclude")
433+
and search_request.fields.exclude
434+
):
435+
for field in search_request.fields.exclude:
436+
fields.append(f"-{field}")
437+
438+
# Convert sortby parameter from POST format to all_collections format
439+
sortby = None
440+
if hasattr(search_request, "sortby") and search_request.sortby:
441+
sort_strings = []
442+
for sort_item in search_request.sortby:
443+
direction = sort_item.get("direction", "asc")
444+
field = sort_item.get("field")
445+
if field:
446+
prefix = "-" if direction.lower() == "desc" else "+"
447+
sort_strings.append(f"{prefix}{field}")
448+
# Join the sort strings into a single string
449+
if sort_strings:
450+
sortby = ",".join(sort_strings)
451+
452+
# Pass all parameters from search_request to all_collections
453+
return await self.all_collections(
454+
limit=search_request.limit if hasattr(search_request, "limit") else None,
455+
fields=fields,
456+
sortby=sortby,
457+
filter_expr=search_request.filter
458+
if hasattr(search_request, "filter")
459+
else None,
460+
filter_lang=search_request.filter_lang
461+
if hasattr(search_request, "filter_lang")
462+
else None,
463+
query=search_request.query if hasattr(search_request, "query") else None,
464+
q=search_request.q if hasattr(search_request, "q") else None,
465+
request=request,
466+
**kwargs,
467+
)
468+
394469
async def get_collection(
395470
self, collection_id: str, **kwargs
396471
) -> stac_types.Collection:

stac_fastapi/core/stac_fastapi/core/utilities.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ def filter_fields( # noqa: C901
9292
This will not perform a deep copy; values of the original item will be referenced
9393
in the return item.
9494
"""
95+
print("filter_fields called with:")
96+
print(" item:", item.keys() if hasattr(item, "keys") else item)
97+
print(" include:", include)
98+
print(" exclude:", exclude)
99+
95100
if not include and not exclude:
101+
print(" No include or exclude, returning original item")
96102
return item
97103

98104
# Build a shallow copy of included fields on an item, or a sub-tree of an item

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/app.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
AggregationExtension,
3939
CollectionSearchExtension,
4040
CollectionSearchFilterExtension,
41+
CollectionSearchPostExtension,
4142
FilterExtension,
4243
FreeTextExtension,
4344
SortExtension,
@@ -136,7 +137,34 @@
136137
)
137138
collections_get_request_model = collection_search_ext.GET
138139

139-
extensions.append(collection_search_ext)
140+
# Create a post request model for collection search
141+
collection_search_post_request_model = create_post_request_model(
142+
collection_search_extensions
143+
)
144+
145+
# Initialize collection search POST extension
146+
collection_search_post_ext = CollectionSearchPostExtension(
147+
client=CoreClient(
148+
database=database_logic,
149+
session=session,
150+
post_request_model=collection_search_post_request_model,
151+
landing_page_id=os.getenv("STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi"),
152+
),
153+
settings=settings,
154+
POST=collection_search_post_request_model,
155+
conformance_classes=[
156+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search",
157+
"http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/simple-query",
158+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter",
159+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#free-text",
160+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#query",
161+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#sort",
162+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#fields",
163+
],
164+
)
165+
166+
extensions.append(collection_search_ext)
167+
extensions.append(collection_search_post_ext)
140168

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

stac_fastapi/opensearch/stac_fastapi/opensearch/app.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
AggregationExtension,
3333
CollectionSearchExtension,
3434
CollectionSearchFilterExtension,
35+
CollectionSearchPostExtension,
3536
FilterExtension,
3637
FreeTextExtension,
3738
SortExtension,
@@ -136,7 +137,34 @@
136137
)
137138
collections_get_request_model = collection_search_ext.GET
138139

139-
extensions.append(collection_search_ext)
140+
# Create a post request model for collection search
141+
collection_search_post_request_model = create_post_request_model(
142+
collection_search_extensions
143+
)
144+
145+
# Initialize collection search POST extension
146+
collection_search_post_ext = CollectionSearchPostExtension(
147+
client=CoreClient(
148+
database=database_logic,
149+
session=session,
150+
post_request_model=collection_search_post_request_model,
151+
landing_page_id=os.getenv("STAC_FASTAPI_LANDING_PAGE_ID", "stac-fastapi"),
152+
),
153+
settings=settings,
154+
POST=collection_search_post_request_model,
155+
conformance_classes=[
156+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search",
157+
"http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/simple-query",
158+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#filter",
159+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#free-text",
160+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#query",
161+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#sort",
162+
"https://api.stacspec.org/v1.0.0-rc.1/collection-search#fields",
163+
],
164+
)
165+
166+
extensions.append(collection_search_ext)
167+
extensions.append(collection_search_post_ext)
140168

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

stac_fastapi/tests/api/test_api_search_collections.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,68 @@ async def test_collections_number_matched_returned(app_client, txn_client, ctx):
598598
# Check that numberMatched matches the number of collections that match the query
599599
# (should be 1 in this case)
600600
assert resp_json["numberMatched"] >= 1
601+
602+
603+
@pytest.mark.asyncio
604+
async def test_collections_search_post(app_client, txn_client, ctx):
605+
"""Verify POST /collections-search endpoint works."""
606+
# Create multiple collections with different ids
607+
base_collection = ctx.collection
608+
609+
# Create collections with ids in a specific order to test search
610+
# Use unique prefixes to avoid conflicts between tests
611+
test_prefix = f"post-{uuid.uuid4().hex[:8]}"
612+
collection_ids = [f"{test_prefix}-{i}" for i in range(10)]
613+
614+
for i, coll_id in enumerate(collection_ids):
615+
test_collection = base_collection.copy()
616+
test_collection["id"] = coll_id
617+
test_collection["title"] = f"Test Collection {i}"
618+
await create_collection(txn_client, test_collection)
619+
620+
await refresh_indices(txn_client)
621+
622+
# Test basic POST search
623+
resp = await app_client.post(
624+
"/collections",
625+
json={"limit": 5},
626+
)
627+
assert resp.status_code == 200
628+
resp_json = resp.json()
629+
630+
# Filter collections to only include the ones we created for this test
631+
test_collections = [
632+
c for c in resp_json["collections"] if c["id"].startswith(test_prefix)
633+
]
634+
635+
# Should return 5 collections
636+
assert len(test_collections) == 5
637+
638+
# Check that numberReturned matches the number of collections returned
639+
assert resp_json["numberReturned"] == len(resp_json["collections"])
640+
641+
# Check that numberMatched is greater than or equal to numberReturned
642+
assert resp_json["numberMatched"] >= resp_json["numberReturned"]
643+
644+
# Test POST search with query
645+
resp = await app_client.post(
646+
"/collections",
647+
json={"query": {"id": {"eq": f"{test_prefix}-1"}}},
648+
)
649+
assert resp.status_code == 200
650+
resp_json = resp.json()
651+
652+
# Filter collections to only include the ones we created for this test
653+
test_collections = [
654+
c for c in resp_json["collections"] if c["id"].startswith(test_prefix)
655+
]
656+
657+
# Should return only 1 collection
658+
assert len(test_collections) == 1
659+
assert test_collections[0]["id"] == f"{test_prefix}-1"
660+
661+
# Check that numberReturned matches the number of collections returned
662+
assert resp_json["numberReturned"] == len(resp_json["collections"])
663+
664+
# Check that numberMatched matches the number of collections that match the query
665+
assert resp_json["numberMatched"] >= 1

0 commit comments

Comments
 (0)