Skip to content

Commit c0616d8

Browse files
committed
feat: remove links from default include list
**sqlalchemy** uses `FieldsExtension.default_includes`, so its fix was pretty *easy -- I did have to add an explicit include+exclude check to the search function to ensure the fields extension was applied _only if_ either `include` or `exclude` was set. **pgstac** was a little trickier, because it doesn't use the `default_includes` list. I did modify one existing test -- `test_app_fields_extension` in the **sqlalchemy** tests was assuming fields would be stripped just by providing a `collections` parameter to the query, which I think was incorrect -- I added an explicit `include` field to the test.
1 parent 94f6e2c commit c0616d8

File tree

6 files changed

+67
-16
lines changed

6 files changed

+67
-16
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class FieldsExtension(ApiExtension):
3939
"stac_version",
4040
"geometry",
4141
"bbox",
42-
"links",
4342
"assets",
4443
"properties.datetime",
4544
"collection",

stac_fastapi/pgstac/stac_fastapi/pgstac/core.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,29 +190,48 @@ async def _search_base(
190190
if include and len(include) == 0:
191191
include = None
192192

193-
async def _add_item_links(
193+
features = collection.get("features", [])
194+
if (
195+
include
196+
and features
197+
and not (
198+
set(include)
199+
& set(f"properties.{p}" for p in features[0].get("properties", []))
200+
)
201+
):
202+
# If the `include` list is only non-existent fields, keep links
203+
exclude_links = False
204+
else:
205+
exclude_links = (
206+
search_request.fields.exclude
207+
and "links" in search_request.fields.exclude
208+
) or (
209+
search_request.fields.include
210+
and "links" not in search_request.fields.include
211+
)
212+
213+
async def _update_item_links(
194214
feature: Item,
195215
collection_id: Optional[str] = None,
196216
item_id: Optional[str] = None,
197217
) -> None:
198-
"""Add ItemLinks to the Item.
218+
"""Update this item's links.
199219
200-
If the fields extension is excluding links, then don't add them.
201-
Also skip links if the item doesn't provide collection and item ids.
220+
If the fields extension is excluding links, or including other
221+
fields but _not_ links, then remove all links.
222+
Also skip adding links if the item doesn't provide collection and item ids.
202223
"""
203224
collection_id = feature.get("collection") or collection_id
204225
item_id = feature.get("id") or item_id
205226

206-
if (
207-
search_request.fields.exclude is None
208-
or "links" not in search_request.fields.exclude
209-
and all([collection_id, item_id])
210-
):
227+
if not exclude_links and all([collection_id, item_id]):
211228
feature["links"] = await ItemLinks(
212229
collection_id=collection_id,
213230
item_id=item_id,
214231
request=request,
215232
).get_links(extra_links=feature.get("links"))
233+
elif exclude_links and "links" in feature:
234+
del feature["links"]
216235

217236
cleaned_features: List[Item] = []
218237

@@ -234,12 +253,12 @@ async def _get_base_item(collection_id: str) -> Dict[str, Any]:
234253
item_id = feature.get("id")
235254

236255
feature = filter_fields(feature, include, exclude)
237-
await _add_item_links(feature, collection_id, item_id)
256+
await _update_item_links(feature, collection_id, item_id)
238257

239258
cleaned_features.append(feature)
240259
else:
241260
for feature in collection.get("features") or []:
242-
await _add_item_links(feature)
261+
await _update_item_links(feature)
243262
cleaned_features.append(feature)
244263

245264
collection["features"] = cleaned_features

stac_fastapi/pgstac/tests/resources/test_item.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,11 +1200,22 @@ async def test_field_extension_exclude_links(
12001200
assert "links" not in resp_json["features"][0]
12011201

12021202

1203+
async def test_field_extension_include_but_not_links(
1204+
app_client, load_test_item, load_test_collection
1205+
):
1206+
# https://github.com/stac-utils/stac-fastapi/issues/395
1207+
body = {"fields": {"include": ["properties.eo:cloud_cover"]}}
1208+
resp = await app_client.post("/search", json=body)
1209+
assert resp.status_code == 200
1210+
resp_json = resp.json()
1211+
assert "links" not in resp_json["features"][0]
1212+
1213+
12031214
async def test_field_extension_include_only_non_existant_field(
12041215
app_client, load_test_item, load_test_collection
12051216
):
1206-
"""Including only a non-existant field should return the full item"""
1207-
body = {"fields": {"include": ["non_existant_field"]}}
1217+
"""Including only a non-existent field should return the full item"""
1218+
body = {"fields": {"include": ["non_existent_field"]}}
12081219

12091220
resp = await app_client.post("/search", json=body)
12101221
assert resp.status_code == 200

stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,9 @@ def post_search(
477477
)
478478

479479
# Use pydantic includes/excludes syntax to implement fields extension
480-
if self.extension_is_enabled("FieldsExtension"):
480+
if self.extension_is_enabled("FieldsExtension") and (
481+
search_request.fields.include or search_request.fields.exclude
482+
):
481483
if search_request.query is not None:
482484
query_include: Set[str] = set(
483485
[

stac_fastapi/sqlalchemy/tests/api/test_api.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,13 @@ def test_app_fields_extension(load_test_data, app_client, postgres_transactions)
142142
item["collection"], item, request=MockStarletteRequest
143143
)
144144

145-
resp = app_client.get("/search", params={"collections": ["test-collection"]})
145+
resp = app_client.post(
146+
"/search",
147+
json={
148+
"collections": ["test-collection"],
149+
"fields": {"include": ["datetime"]},
150+
},
151+
)
146152
assert resp.status_code == 200
147153
resp_json = resp.json()
148154
assert list(resp_json["features"][0]["properties"]) == ["datetime"]

stac_fastapi/sqlalchemy/tests/resources/test_item.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,20 @@ def test_field_extension_exclude_default_includes(app_client, load_test_data):
879879
assert "geometry" not in resp_json["features"][0]
880880

881881

882+
def test_field_extension_include_but_not_links(app_client, load_test_data):
883+
# https://github.com/stac-utils/stac-fastapi/issues/395
884+
test_item = load_test_data("test_item.json")
885+
resp = app_client.post(
886+
f"/collections/{test_item['collection']}/items", json=test_item
887+
)
888+
assert resp.status_code == 200
889+
body = {"fields": {"include": ["properties.eo:cloud_cover"]}}
890+
resp = app_client.post("/search", json=body)
891+
assert resp.status_code == 200
892+
resp_json = resp.json()
893+
assert "links" not in resp_json["features"][0]
894+
895+
882896
def test_search_intersects_and_bbox(app_client):
883897
"""Test POST search intersects and bbox are mutually exclusive (core)"""
884898
bbox = [-118, 34, -117, 35]

0 commit comments

Comments
 (0)