Skip to content

Commit 3c3b5cb

Browse files
author
Andrzej Pijanowski
committed
feat: add support for excluding queryables from validation
1 parent 5de02fc commit 3c3b5cb

File tree

3 files changed

+46
-1
lines changed

3 files changed

+46
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,9 @@ QUERYABLES_CACHE_TTL=3600 # Optional, defaults to 3600 seconds (1 hour)
445445
- Search requests (both GET and POST) are checked against this cache.
446446
- If a request contains a query parameter or filter field that is not in the list of allowed queryables, the API returns a `400 Bad Request` error with a message indicating the invalid field(s).
447447
- The cache is automatically refreshed based on the `QUERYABLES_CACHE_TTL` setting.
448+
- **Interaction with `EXCLUDED_FROM_QUERYABLES`**: If `VALIDATE_QUERYABLES` is enabled, fields listed in `EXCLUDED_FROM_QUERYABLES` will also be considered invalid for filtering. This effectively enforces the exclusion of these fields from search queries.
448449

449-
This feature helps prevent queries on unindexed fields which could lead to poor performance or unexpected results.
450+
This feature helps prevent queries on non-queryable fields which could lead to unnecessary load on the database.
450451

451452
## Datetime-Based Index Management
452453

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/queryables.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(self, database_logic: Any):
2727
self._lock = asyncio.Lock()
2828
self.validation_enabled: bool = False
2929
self.cache_ttl: int = 3600 # How often to refresh cache (in seconds)
30+
self.excluded_queryables: Set[str] = set()
3031
self.reload_settings()
3132

3233
def reload_settings(self):
@@ -36,6 +37,17 @@ def reload_settings(self):
3637
)
3738
self.cache_ttl = int(os.getenv("QUERYABLES_CACHE_TTL", "3600"))
3839

40+
excluded = os.getenv("EXCLUDED_FROM_QUERYABLES", "")
41+
self.excluded_queryables = set()
42+
if excluded:
43+
for field in excluded.split(","):
44+
field = field.strip()
45+
if field:
46+
# Remove 'properties.' prefix if present
47+
if field.startswith("properties."):
48+
field = field[11:]
49+
self.excluded_queryables.add(field)
50+
3951
async def _update_cache(self):
4052
"""Update the cache with the latest queryables from the database."""
4153
if not self.validation_enabled:
@@ -48,6 +60,9 @@ async def _update_cache(self):
4860
queryables_mapping = await self._db_logic.get_queryables_mapping()
4961
all_queryables_set = set(queryables_mapping.keys())
5062

63+
if self.excluded_queryables:
64+
all_queryables_set = all_queryables_set - self.excluded_queryables
65+
5166
self._all_queryables = all_queryables_set
5267

5368
self._cache = {"*": list(all_queryables_set)}

stac_fastapi/tests/api/test_api_query_validation.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,32 @@ async def test_item_collection_get_filter_invalid_param(app_client, ctx):
6565
assert resp.status_code == 400
6666
resp_json = resp.json()
6767
assert "Invalid query fields: invalid_param" in resp_json["detail"]
68+
69+
70+
@pytest.mark.asyncio
71+
async def test_validate_queryables_excluded(app_client, ctx):
72+
"""Test that excluded queryables are rejected when validation is enabled."""
73+
74+
excluded_field = "eo:cloud_cover"
75+
76+
with mock.patch.dict(
77+
os.environ,
78+
{
79+
"VALIDATE_QUERYABLES": "true",
80+
"EXCLUDED_FROM_QUERYABLES": excluded_field,
81+
"QUERYABLES_CACHE_TTL": "0",
82+
},
83+
):
84+
reload_queryables_settings()
85+
86+
query = {"query": {excluded_field: {"lt": 10}}}
87+
resp = await app_client.post("/search", json=query)
88+
assert resp.status_code == 400
89+
assert "Invalid query fields" in resp.json()["detail"]
90+
assert excluded_field in resp.json()["detail"]
91+
92+
query = {"query": {"id": {"eq": "test-item"}}}
93+
resp = await app_client.post("/search", json=query)
94+
assert resp.status_code == 200
95+
96+
reload_queryables_settings()

0 commit comments

Comments
 (0)