Repository: stac-utils/stac-fastapi
Pull request #784 (2025-01-17 08:51:09)

rename `filter` to `filter_expr` to avoid python method conflict
closes https://github.com/stac-utils/stac-fastapi/issues/754



Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 0d23d55..67cee3e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -5,6 +5,7 @@
 ## Changed
 
 * use `string` type instead of python `datetime.datetime` for datetime parameter in `BaseSearchGetRequest`, `ItemCollectionUri` and `BaseCollectionSearchGetRequest` GET models
+* rename `filter` to `filter_expr` for `FilterExtensionGetRequest` and `FilterExtensionPostRequest` attributes to avoid conflict with python filter method
 
 ## [3.0.5] - 2025-01-10
 
diff --git a/stac_fastapi/extensions/stac_fastapi/extensions/core/filter/request.py b/stac_fastapi/extensions/stac_fastapi/extensions/core/filter/request.py
index dae56a5..b87c63b 100644
--- a/stac_fastapi/extensions/stac_fastapi/extensions/core/filter/request.py
+++ b/stac_fastapi/extensions/stac_fastapi/extensions/core/filter/request.py
@@ -16,9 +16,10 @@ FilterLang = Literal["cql-json", "cql2-json", "cql2-text"]
 class FilterExtensionGetRequest(APIRequest):
     """Filter extension GET request model."""
 
-    filter: Annotated[
+    filter_expr: Annotated[
         Optional[str],
         Query(
+            alias="filter",
             description="""A CQL filter expression for filtering items.\n
 Supports `CQL-JSON` as defined in https://portal.ogc.org/files/96288\n
 Remember to URL encode the CQL-JSON if using GET""",
@@ -46,8 +47,9 @@ Remember to URL encode the CQL-JSON if using GET""",
 class FilterExtensionPostRequest(BaseModel):
     """Filter extension POST request model."""
 
-    filter: Optional[Dict[str, Any]] = Field(
+    filter_expr: Optional[Dict[str, Any]] = Field(
         default=None,
+        alias="filter",
         description="A CQL filter expression for filtering items.",
         json_schema_extra={
             "example": {


Test patch:
diff --git a/stac_fastapi/api/tests/test_app.py b/stac_fastapi/api/tests/test_app.py
index 106bde3..59b09d9 100644
--- a/stac_fastapi/api/tests/test_app.py
+++ b/stac_fastapi/api/tests/test_app.py
@@ -126,7 +126,7 @@ def test_filter_extension(validate, TestCoreClient, item_dict):
             self, search_request: BaseSearchPostRequest, **kwargs
         ) -> stac.ItemCollection:
             search_request.collections = ["test"]
-            search_request.filter = {}
+            search_request.filter_expr = {}
             search_request.filter_crs = "EPSG:4326"
             search_request.filter_lang = "cql2-text"
 
@@ -142,23 +142,14 @@ def test_filter_extension(validate, TestCoreClient, item_dict):
             intersects: Optional[str] = None,
             datetime: Optional[str] = None,
             limit: Optional[int] = 10,
-            filter: Optional[str] = None,
+            filter_expr: Optional[str] = None,
             filter_crs: Optional[str] = None,
             filter_lang: Optional[str] = None,
             **kwargs,
         ) -> stac.ItemCollection:
-            # Check if all filter parameters are passed correctly
-
-            assert filter == "TEST"
-
-            # FIXME: https://github.com/stac-utils/stac-fastapi/issues/638
-            # hyphen alias for filter_crs and filter_lang are currently not working
-            # Query parameters `filter-crs` and `filter-lang`
-            # should be recognized by the API
-            # They are present in the `request.query_params` but not in the `kwargs`
-
-            # assert filter_crs == "EPSG:4326"
-            # assert filter_lang == "cql2-text"
+            assert filter_expr == "TEST"
+            assert filter_crs == "EPSG:4326"
+            assert filter_lang == "cql2-text"
 
             return stac.ItemCollection(
                 type="FeatureCollection", features=[stac.Item(**item_dict)]
diff --git a/stac_fastapi/api/tests/test_models.py b/stac_fastapi/api/tests/test_models.py
index 0c25e7f..283c79f 100644
--- a/stac_fastapi/api/tests/test_models.py
+++ b/stac_fastapi/api/tests/test_models.py
@@ -28,12 +28,13 @@ def test_create_get_request_model():
         ),
         datetime="2020-01-01T00:00:00.00001Z",
         limit=10,
-        filter="test==test",
+        filter_expr="test==test",
         filter_crs="epsg:4326",
         filter_lang="cql2-text",
     )
 
     assert model.collections == ["test1", "test2"]
+    assert model.filter_expr == "test==test"
     assert model.filter_crs == "epsg:4326"
     d = model.start_date
     assert d.microsecond == 10
@@ -64,6 +65,7 @@ def test_create_get_request_model():
             "/test",
             params={
                 "collections": "test1,test2",
+                "filter": "test=test",
                 "filter-crs": "epsg:4326",
                 "filter-lang": "cql2-text",
             },
@@ -71,6 +73,7 @@ def test_create_get_request_model():
         assert resp.status_code == 200
         response_dict = resp.json()
         assert response_dict["collections"] == ["test1", "test2"]
+        assert response_dict["filter_expr"] == "test=test"
         assert response_dict["filter_crs"] == "epsg:4326"
         assert response_dict["filter_lang"] == "cql2-text"
 
@@ -89,19 +92,22 @@ def test_create_post_request_model(filter_val, passes):
         with pytest.raises(ValidationError):
             model = request_model(filter=filter_val)
     else:
-        model = request_model(
-            collections=["test1", "test2"],
-            ids=["test1", "test2"],
-            bbox=[0, 0, 1, 1],
-            datetime="2020-01-01T00:00:00.00001Z",
-            limit=10,
-            filter=filter_val,
-            **{"filter-crs": "epsg:4326", "filter-lang": "cql2-json"},
+        model = request_model.model_validate(
+            {
+                "collections": ["test1", "test2"],
+                "ids": ["test1", "test2"],
+                "bbox": [0, 0, 1, 1],
+                "datetime": "2020-01-01T00:00:00.00001Z",
+                "limit": 10,
+                "filter": filter_val,
+                "filter-crs": "epsg:4326",
+                "filter-lang": "cql2-json",
+            }
         )
 
         assert model.collections == ["test1", "test2"]
+        assert model.filter_expr == filter_val
         assert model.filter_crs == "epsg:4326"
-        assert model.filter == filter_val
         assert model.datetime == "2020-01-01T00:00:00.00001Z"
 
     with pytest.raises(ValidationError):
diff --git a/stac_fastapi/extensions/tests/test_collection_search.py b/stac_fastapi/extensions/tests/test_collection_search.py
index 3b7bcfc..83de85e 100644
--- a/stac_fastapi/extensions/tests/test_collection_search.py
+++ b/stac_fastapi/extensions/tests/test_collection_search.py
@@ -185,7 +185,7 @@ def test_collection_search_extension_models():
         assert "datetime" in response_dict
         assert "limit" in response_dict
         assert "q" in response_dict
-        assert "filter" in response_dict
+        assert "filter_expr" in response_dict
         assert "query" in response_dict
         assert "sortby" in response_dict
         assert "fields" in response_dict
@@ -211,7 +211,9 @@ def test_collection_search_extension_models():
         assert "2020-06-13T13:00:00Z/2020-06-13T14:00:00Z" == response_dict["datetime"]
         assert 100 == response_dict["limit"]
         assert ["EO", "Earth Observation"] == response_dict["q"]
-        assert "id='item_id' AND collection='collection_id'" == response_dict["filter"]
+        assert (
+            "id='item_id' AND collection='collection_id'" == response_dict["filter_expr"]
+        )
         assert "filter_crs" in response_dict
         assert "cql2-text" in response_dict["filter_lang"]
         assert "query" in response_dict
@@ -347,7 +349,7 @@ def test_collection_search_extension_post_models():
         assert "datetime" in response_dict
         assert "limit" in response_dict
         assert "q" in response_dict
-        assert "filter" in response_dict
+        assert "filter_expr" in response_dict
         assert "query" in response_dict
         assert "sortby" in response_dict
         assert "fields" in response_dict
@@ -388,7 +390,7 @@ def test_collection_search_extension_post_models():
         assert "2020-06-13T13:00:00Z/2020-06-13T14:00:00Z" == response_dict["datetime"]
         assert 10_000 == response_dict["limit"]
         assert ["EO", "Earth Observation"] == response_dict["q"]
-        assert response_dict["filter"]
+        assert response_dict["filter_expr"]
         assert "filter_crs" in response_dict
         assert "cql2-json" in response_dict["filter_lang"]
         assert response_dict["query"]
@@ -433,7 +435,7 @@ def test_from_extensions_methods(extensions):
     assert hasattr(collection_search, "fields")
     assert hasattr(collection_search, "q")
     assert hasattr(collection_search, "sortby")
-    assert hasattr(collection_search, "filter")
+    assert hasattr(collection_search, "filter_expr")
     assert ext.conformance_classes == [
         ConformanceClasses.COLLECTIONSEARCH,
         ConformanceClasses.BASIS,
@@ -457,7 +459,7 @@ def test_from_extensions_methods(extensions):
     assert hasattr(collection_search, "fields")
     assert hasattr(collection_search, "q")
     assert hasattr(collection_search, "sortby")
-    assert hasattr(collection_search, "filter")
+    assert hasattr(collection_search, "filter_expr")
     assert ext.conformance_classes == [
         ConformanceClasses.COLLECTIONSEARCH,
         ConformanceClasses.BASIS,
diff --git a/stac_fastapi/extensions/tests/test_filter.py b/stac_fastapi/extensions/tests/test_filter.py
index 85db526..32152c4 100644
--- a/stac_fastapi/extensions/tests/test_filter.py
+++ b/stac_fastapi/extensions/tests/test_filter.py
@@ -57,6 +57,7 @@ def test_search_filter_post_filter_lang_default(client: TestClient):
     )
     assert response.is_success, response.json()
     response_dict = response.json()
+    assert response_dict["filter_expr"]
     assert response_dict["filter_lang"] == "cql2-json"
 
 
@@ -73,6 +74,7 @@ def test_search_filter_post_filter_lang_non_default(client: TestClient):
     )
     assert response.is_success, response.json()
     response_dict = response.json()
+    assert response_dict["filter_expr"]
     assert response_dict["filter_lang"] == filter_lang_value
 
 
@@ -87,7 +89,7 @@ def test_search_filter_get(client: TestClient):
     assert response.is_success, response.json()
     response_dict = response.json()
     assert not response_dict["collections"]
-    assert response_dict["filter"] == "id='item_id' AND collection='collection_id'"
+    assert response_dict["filter_expr"] == "id='item_id' AND collection='collection_id'"
     assert not response_dict["filter_crs"]
     assert response_dict["filter_lang"] == "cql2-text"
 
@@ -102,7 +104,7 @@ def test_search_filter_get(client: TestClient):
     response_dict = response.json()
     assert not response_dict["collections"]
     assert (
-        response_dict["filter"]
+        response_dict["filter_expr"]
         == "{'op': '=', 'args': [{'property': 'id'}, 'test-item']}"
     )
     assert not response_dict["filter_crs"]