diff --git a/space2stats_api/src/space2stats/api/app.py b/space2stats_api/src/space2stats/api/app.py index 1556543..06da597 100644 --- a/space2stats_api/src/space2stats/api/app.py +++ b/space2stats_api/src/space2stats/api/app.py @@ -1,4 +1,5 @@ from contextlib import asynccontextmanager +from textwrap import dedent from typing import Any, Dict, List, Optional import boto3 @@ -6,10 +7,11 @@ from asgi_s3_response_middleware import S3ResponseMiddleware from fastapi import Depends, FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import ORJSONResponse +from fastapi.responses import ORJSONResponse, RedirectResponse from starlette.requests import Request from starlette_cramjam.middleware import CompressionMiddleware +from .. import __version__ from ..lib import StatsTable from .db import close_db_connection, connect_to_db from .errors import add_exception_handlers @@ -31,6 +33,29 @@ async def lifespan(app: FastAPI): app = FastAPI( default_response_class=ORJSONResponse, lifespan=lifespan, + title="World Bank Space2Stats API", + version=__version__, + summary="API for Space2Stats", + description=dedent( + """ + The Space2Stats program is designed to provide academics, statisticians, and data + scientists with easier access to regularly requested geospatial aggregate data. + + Geographic variables were generated at the hexagon (h3) level 6 and this API enables + users to query the data by area of interest and generate aggregate statistics efficiently. + + For more information on the datasets available and usage examples, see the [Space2Stats docs](https://worldbank.github.io/DECAT_Space2Stats/readme.html). + """ + ), + contact={ + "name": "Benjamin Stewart (Task Leader), Development Data Group (DECDG), Worldbank", + "url": "https://data.worldbank.org", + "email": "bstewart@worldbankgroup.org", + }, + license_info={ + "name": "MIT", + "url": "https://opensource.org/licenses/MIT", + }, ) app.add_middleware( @@ -55,6 +80,60 @@ def stats_table(request: Request): @app.post("/summary", response_model=List[Dict[str, Any]]) def get_summary(body: SummaryRequest, table: StatsTable = Depends(stats_table)): + """Retrieve Statistics from a GeoJSON feature. + + Parameters + ---------- + +
+
aoi
+
+ + `GeoJSON Feature` + + The Area of Interest, either as a `Feature` or an instance of `AoiModel` +
+ +
spatial_join_method
+
+ + `["touches", "centroid", "within"]` + + The method to use for performing the spatial join between the AOI and H3 cells + + - `touches`: Includes H3 cells that touch the AOI + - `centroid`: Includes H3 cells where the centroid falls within the AOI + - `within`: Includes H3 cells entirely within the AOI + +
+ +
fields
+
+ + `List[str]` + + A list of field names to retrieve from the statistics table. +
+ +
geometry
+
+ + `Optional["polygon", "point"]` + + Specifies if the H3 geometries should be included in the response. It can be either "polygon" or "point". If None, geometries are not included +
+
+ + Returns + ------- + `List[Dict]` + + A list of dictionaries containing statistical summaries for each H3 cell. Each dictionary contains: + + - `hex_id`: The H3 cell identifier + - `geometry` (optional): The geometry of the H3 cell, if geometry is specified. + - Other fields from the statistics table, based on the specified `fields` + """ try: return table.summaries( body.aoi, @@ -67,6 +146,54 @@ def get_summary(body: SummaryRequest, table: StatsTable = Depends(stats_table)): @app.post("/aggregate", response_model=Dict[str, float]) def get_aggregate(body: AggregateRequest, table: StatsTable = Depends(stats_table)): + """Aggregate Statistics from a GeoJSON feature. + + Parameters + ---------- + +
+
aoi
+
+ + `GeoJSON Feature` + + The Area of Interest, either as a `Feature` or an instance of `AoiModel` +
+ +
spatial_join_method
+
+ + `["touches", "centroid", "within"]` + + The method to use for performing the spatial join between the AOI and H3 cells + + - `touches`: Includes H3 cells that touch the AOI + - `centroid`: Includes H3 cells where the centroid falls within the AOI + - `within`: Includes H3 cells entirely within the AOI + +
+ +
fields
+
+ + `List[str]` + + A list of field names to retrieve from the statistics table. +
+ +
aggregation_type
+
+ + `["sum", "avg", "count", "max", "min"]` + + The manner in which to aggregate the statistics. +
+
+ + Returns + ------- + `Dict[str, float]` + """ try: return table.aggregate( aoi=body.aoi, @@ -79,11 +206,20 @@ def get_aggregate(body: AggregateRequest, table: StatsTable = Depends(stats_tabl @app.get("/fields", response_model=List[str]) def fields(table: StatsTable = Depends(stats_table)): + """Fields available in the statistics table""" return table.fields() + @app.get("/metadata") + def metadata_redirect(): + """Redirect to project STAC Browser.""" + return RedirectResponse( + "https://radiantearth.github.io/stac-browser/#/external/raw.githubusercontent.com/worldbank/DECAT_Space2Stats/refs/heads/main/space2stats_api/src/space2stats_ingest/METADATA/stac/catalog.json" + ) + @app.get("/") - def read_root(): - return {"message": "Welcome to Space2Stats!"} + def docs_redirect(): + """Redirect to project documentation.""" + return RedirectResponse("https://worldbank.github.io/DECAT_Space2Stats") @app.get("/health") def health(): diff --git a/space2stats_api/src/tests/test_api.py b/space2stats_api/src/tests/test_api.py index f57abf3..ffc804e 100644 --- a/space2stats_api/src/tests/test_api.py +++ b/space2stats_api/src/tests/test_api.py @@ -20,9 +20,27 @@ def test_read_root(client): - response = client.get("/") - assert response.status_code == 200 - assert response.json() == {"message": "Welcome to Space2Stats!"} + response = client.get("/", follow_redirects=False) + assert response.status_code in [ + 302, + 307, + ], f"Unexpected status code: {response.status_code}" + assert ( + response.headers["Location"] == "https://worldbank.github.io/DECAT_Space2Stats" + ) + + +def test_metadata_redirect(client): + response = client.get("/metadata", follow_redirects=False) + assert response.status_code in [ + 302, + 307, + ], f"Unexpected status code: {response.status_code}" + assert response.headers["Location"] == ( + "https://radiantearth.github.io/stac-browser/#/external/raw.githubusercontent.com/" + "worldbank/DECAT_Space2Stats/refs/heads/main/space2stats_api/src/space2stats_ingest/" + "METADATA/stac/catalog.json" + ) def test_get_summary(client):