From 1e25581cb521d89ec30663f7c95c4fbe7865ad79 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Tue, 17 Aug 2021 08:46:53 -0500 Subject: [PATCH 1/4] Correct response codes for bad/unusable bboxes --- .../pgstac/stac_fastapi/pgstac/core.py | 8 +++++++- .../pgstac/tests/resources/test_item.py | 18 ++++++++++++++++++ .../sqlalchemy/stac_fastapi/sqlalchemy/core.py | 12 ++++++++++-- .../sqlalchemy/tests/resources/test_item.py | 18 ++++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py index 7542a934f..600b57933 100644 --- a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py +++ b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py @@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Union import attr +from fastapi import HTTPException import orjson from buildpg import render from starlette.requests import Request @@ -188,6 +189,8 @@ async def post_search( Returns: ItemCollection containing items which match the search criteria. """ + if search_request.bbox and len(search_request.bbox) == 6: + raise HTTPException(status_code=501, detail="Support for 3D bounding boxes is not yet implemented") item_collection = await self._search_base(search_request, **kwargs) return ItemCollection(**item_collection) @@ -250,5 +253,8 @@ async def get_search( base_args["fields"] = {"include": includes, "exclude": excludes} # Do the request - search_request = PgstacSearch(**base_args) + try: + search_request = PgstacSearch(**base_args) + except: + raise HTTPException(status_code=400, detail="Invalid parameters provided") return await self.post_search(search_request, request=kwargs["request"]) diff --git a/stac_fastapi/pgstac/tests/resources/test_item.py b/stac_fastapi/pgstac/tests/resources/test_item.py index 31a69a0f4..d1fc7c2cf 100644 --- a/stac_fastapi/pgstac/tests/resources/test_item.py +++ b/stac_fastapi/pgstac/tests/resources/test_item.py @@ -928,3 +928,21 @@ async def test_relative_link_construction(): ) links = CollectionLinks(collection_id="naip", request=req) assert links.link_items()["href"] == "http://test/stac/collections/naip/items" + +@pytest.mark.asyncio +async def test_search_bbox_errors(app_client): + body = {"query": { "bbox": [0]}} + resp = await app_client.post("/search", json=body) + assert resp.status_code == 400 + + body = {"query": { "bbox": [100.0, 0.0, 0.0, 105.0, 1.0, 1.0]}} + resp = await app_client.post("/search", json=body) + assert resp.status_code == 400 + + params = {"bbox": "0,0,0,1,1,1"} + resp = await app_client.get("/search", params=params) + assert resp.status_code == 501 + + params = {"bbox": "100.0,0.0,0.0,105.0"} + resp = await app_client.get("/search", params=params) + assert resp.status_code == 400 diff --git a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py index 224a163bf..65e34970e 100644 --- a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py +++ b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py @@ -7,6 +7,7 @@ import attr import geoalchemy2 as ga +from fastapi import HTTPException import sqlalchemy as sa import stac_pydantic from shapely.geometry import Polygon as ShapelyPolygon @@ -207,7 +208,10 @@ def get_search( base_args["fields"] = {"include": includes, "exclude": excludes} # Do the request - search_request = SQLAlchemySTACSearch(**base_args) + try: + search_request = SQLAlchemySTACSearch(**base_args) + except: + raise HTTPException(status_code=400, detail="Invalid parameters provided") resp = self.post_search(search_request, request=kwargs["request"]) # Pagination @@ -293,7 +297,11 @@ def post_search( if search_request.intersects is not None: poly = shape(search_request.intersects) elif search_request.bbox: - poly = ShapelyPolygon.from_bounds(*search_request.bbox) + if len(search_request.bbox) == 4: + poly = ShapelyPolygon.from_bounds(*search_request.bbox) + elif len(search_request.bbox) == 6: + raise HTTPException(status_code=501, detail="Support for 3D bounding boxes is not yet implemented") + if poly: filter_geom = ga.shape.from_shape(poly, srid=4326) diff --git a/stac_fastapi/sqlalchemy/tests/resources/test_item.py b/stac_fastapi/sqlalchemy/tests/resources/test_item.py index 29b5eccf3..304b17bdc 100644 --- a/stac_fastapi/sqlalchemy/tests/resources/test_item.py +++ b/stac_fastapi/sqlalchemy/tests/resources/test_item.py @@ -762,6 +762,24 @@ def test_search_invalid_query_field(app_client): assert resp.status_code == 400 +def test_search_bbox_errors(app_client): + body = {"query": { "bbox": [0]}} + resp = app_client.post("/search", json=body) + assert resp.status_code == 400 + + body = {"query": { "bbox": [100.0, 0.0, 0.0, 105.0, 1.0, 1.0]}} + resp = app_client.post("/search", json=body) + assert resp.status_code == 400 + + params = {"bbox": "0,0,0,1,1,1"} + resp = app_client.get("/search", params=params) + assert resp.status_code == 501 + + params = {"bbox": "100.0,0.0,0.0,105.0"} + resp = app_client.get("/search", params=params) + assert resp.status_code == 400 + + def test_conformance_classes_configurable(): """Test conformance class configurability""" landing = LandingPageMixin() From 8d6dfb41d237dd31bf47e556d228b54ba2819150 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Tue, 17 Aug 2021 08:53:03 -0500 Subject: [PATCH 2/4] lint --- stac_fastapi/pgstac/stac_fastapi/pgstac/core.py | 7 +++++-- stac_fastapi/pgstac/tests/resources/test_item.py | 5 +++-- stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py | 8 +++++--- stac_fastapi/sqlalchemy/tests/resources/test_item.py | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py index 600b57933..d27802494 100644 --- a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py +++ b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py @@ -4,9 +4,9 @@ from typing import Any, Dict, List, Optional, Union import attr -from fastapi import HTTPException import orjson from buildpg import render +from fastapi import HTTPException from starlette.requests import Request from stac_fastapi.pgstac.models.links import CollectionLinks, ItemLinks, PagingLinks @@ -190,7 +190,10 @@ async def post_search( ItemCollection containing items which match the search criteria. """ if search_request.bbox and len(search_request.bbox) == 6: - raise HTTPException(status_code=501, detail="Support for 3D bounding boxes is not yet implemented") + raise HTTPException( + status_code=501, + detail="Support for 3D bounding boxes is not yet implemented", + ) item_collection = await self._search_base(search_request, **kwargs) return ItemCollection(**item_collection) diff --git a/stac_fastapi/pgstac/tests/resources/test_item.py b/stac_fastapi/pgstac/tests/resources/test_item.py index d1fc7c2cf..b16377454 100644 --- a/stac_fastapi/pgstac/tests/resources/test_item.py +++ b/stac_fastapi/pgstac/tests/resources/test_item.py @@ -929,13 +929,14 @@ async def test_relative_link_construction(): links = CollectionLinks(collection_id="naip", request=req) assert links.link_items()["href"] == "http://test/stac/collections/naip/items" + @pytest.mark.asyncio async def test_search_bbox_errors(app_client): - body = {"query": { "bbox": [0]}} + body = {"query": {"bbox": [0]}} resp = await app_client.post("/search", json=body) assert resp.status_code == 400 - body = {"query": { "bbox": [100.0, 0.0, 0.0, 105.0, 1.0, 1.0]}} + body = {"query": {"bbox": [100.0, 0.0, 0.0, 105.0, 1.0, 1.0]}} resp = await app_client.post("/search", json=body) assert resp.status_code == 400 diff --git a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py index 65e34970e..4b68a2905 100644 --- a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py +++ b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py @@ -7,9 +7,9 @@ import attr import geoalchemy2 as ga -from fastapi import HTTPException import sqlalchemy as sa import stac_pydantic +from fastapi import HTTPException from shapely.geometry import Polygon as ShapelyPolygon from shapely.geometry import shape from sqlakeyset import get_page @@ -300,8 +300,10 @@ def post_search( if len(search_request.bbox) == 4: poly = ShapelyPolygon.from_bounds(*search_request.bbox) elif len(search_request.bbox) == 6: - raise HTTPException(status_code=501, detail="Support for 3D bounding boxes is not yet implemented") - + raise HTTPException( + status_code=501, + detail="Support for 3D bounding boxes is not yet implemented", + ) if poly: filter_geom = ga.shape.from_shape(poly, srid=4326) diff --git a/stac_fastapi/sqlalchemy/tests/resources/test_item.py b/stac_fastapi/sqlalchemy/tests/resources/test_item.py index 304b17bdc..5408ed3b2 100644 --- a/stac_fastapi/sqlalchemy/tests/resources/test_item.py +++ b/stac_fastapi/sqlalchemy/tests/resources/test_item.py @@ -763,11 +763,11 @@ def test_search_invalid_query_field(app_client): def test_search_bbox_errors(app_client): - body = {"query": { "bbox": [0]}} + body = {"query": {"bbox": [0]}} resp = app_client.post("/search", json=body) assert resp.status_code == 400 - body = {"query": { "bbox": [100.0, 0.0, 0.0, 105.0, 1.0, 1.0]}} + body = {"query": {"bbox": [100.0, 0.0, 0.0, 105.0, 1.0, 1.0]}} resp = app_client.post("/search", json=body) assert resp.status_code == 400 From 5016794a6433ca67fe3cfd31fa6e3d9a341cbd0f Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Tue, 17 Aug 2021 09:24:12 -0500 Subject: [PATCH 3/4] Remove bare exceptions --- stac_fastapi/pgstac/stac_fastapi/pgstac/core.py | 3 ++- stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py index d27802494..cfb1a767e 100644 --- a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py +++ b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py @@ -7,6 +7,7 @@ import orjson from buildpg import render from fastapi import HTTPException +from pydantic import ValidationError from starlette.requests import Request from stac_fastapi.pgstac.models.links import CollectionLinks, ItemLinks, PagingLinks @@ -258,6 +259,6 @@ async def get_search( # Do the request try: search_request = PgstacSearch(**base_args) - except: + except ValidationError: raise HTTPException(status_code=400, detail="Invalid parameters provided") return await self.post_search(search_request, request=kwargs["request"]) diff --git a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py index 4b68a2905..838df9e94 100644 --- a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py +++ b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py @@ -10,6 +10,7 @@ import sqlalchemy as sa import stac_pydantic from fastapi import HTTPException +from pydantic import ValidationError from shapely.geometry import Polygon as ShapelyPolygon from shapely.geometry import shape from sqlakeyset import get_page @@ -210,7 +211,7 @@ def get_search( # Do the request try: search_request = SQLAlchemySTACSearch(**base_args) - except: + except ValidationError: raise HTTPException(status_code=400, detail="Invalid parameters provided") resp = self.post_search(search_request, request=kwargs["request"]) From c90526d413cf22c185786c5e29e97e973aedcb76 Mon Sep 17 00:00:00 2001 From: Nathan Zimmerman Date: Wed, 18 Aug 2021 10:08:53 -0500 Subject: [PATCH 4/4] Remove 501 usage from bbox handling --- stac_fastapi/pgstac/stac_fastapi/pgstac/core.py | 5 ----- stac_fastapi/pgstac/tests/resources/test_item.py | 4 ---- .../sqlalchemy/stac_fastapi/sqlalchemy/core.py | 12 ++++++++---- stac_fastapi/sqlalchemy/tests/resources/test_item.py | 4 ---- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py index cfb1a767e..384388e7d 100644 --- a/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py +++ b/stac_fastapi/pgstac/stac_fastapi/pgstac/core.py @@ -190,11 +190,6 @@ async def post_search( Returns: ItemCollection containing items which match the search criteria. """ - if search_request.bbox and len(search_request.bbox) == 6: - raise HTTPException( - status_code=501, - detail="Support for 3D bounding boxes is not yet implemented", - ) item_collection = await self._search_base(search_request, **kwargs) return ItemCollection(**item_collection) diff --git a/stac_fastapi/pgstac/tests/resources/test_item.py b/stac_fastapi/pgstac/tests/resources/test_item.py index b16377454..b1a4f0f62 100644 --- a/stac_fastapi/pgstac/tests/resources/test_item.py +++ b/stac_fastapi/pgstac/tests/resources/test_item.py @@ -940,10 +940,6 @@ async def test_search_bbox_errors(app_client): resp = await app_client.post("/search", json=body) assert resp.status_code == 400 - params = {"bbox": "0,0,0,1,1,1"} - resp = await app_client.get("/search", params=params) - assert resp.status_code == 501 - params = {"bbox": "100.0,0.0,0.0,105.0"} resp = await app_client.get("/search", params=params) assert resp.status_code == 400 diff --git a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py index 838df9e94..d25fb2372 100644 --- a/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py +++ b/stac_fastapi/sqlalchemy/stac_fastapi/sqlalchemy/core.py @@ -301,10 +301,14 @@ def post_search( if len(search_request.bbox) == 4: poly = ShapelyPolygon.from_bounds(*search_request.bbox) elif len(search_request.bbox) == 6: - raise HTTPException( - status_code=501, - detail="Support for 3D bounding boxes is not yet implemented", - ) + """Shapely doesn't support 3d bounding boxes we'll just use the 2d portion""" + bbox_2d = [ + search_request.bbox[0], + search_request.bbox[1], + search_request.bbox[3], + search_request.bbox[4], + ] + poly = ShapelyPolygon.from_bounds(*bbox_2d) if poly: filter_geom = ga.shape.from_shape(poly, srid=4326) diff --git a/stac_fastapi/sqlalchemy/tests/resources/test_item.py b/stac_fastapi/sqlalchemy/tests/resources/test_item.py index 5408ed3b2..0639167e6 100644 --- a/stac_fastapi/sqlalchemy/tests/resources/test_item.py +++ b/stac_fastapi/sqlalchemy/tests/resources/test_item.py @@ -771,10 +771,6 @@ def test_search_bbox_errors(app_client): resp = app_client.post("/search", json=body) assert resp.status_code == 400 - params = {"bbox": "0,0,0,1,1,1"} - resp = app_client.get("/search", params=params) - assert resp.status_code == 501 - params = {"bbox": "100.0,0.0,0.0,105.0"} resp = app_client.get("/search", params=params) assert resp.status_code == 400