Skip to content

Commit

Permalink
Merge pull request #99 from worldbank/docs/api-docs
Browse files Browse the repository at this point in the history
First draft at improved API docs
  • Loading branch information
andresfchamorro authored Dec 9, 2024
2 parents 6516101 + e42c34c commit 104ad65
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 6 deletions.
142 changes: 139 additions & 3 deletions space2stats_api/src/space2stats/api/app.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from contextlib import asynccontextmanager
from textwrap import dedent
from typing import Any, Dict, List, Optional

import boto3
import psycopg as pg
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
Expand All @@ -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": "[email protected]",
},
license_info={
"name": "MIT",
"url": "https://opensource.org/licenses/MIT",
},
)

app.add_middleware(
Expand All @@ -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
----------
<dl>
<dt>aoi</dt>
<dd>
`GeoJSON Feature`
The Area of Interest, either as a `Feature` or an instance of `AoiModel`
</dd>
<dt>spatial_join_method</dt>
<dd>
`["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
</dd>
<dt>fields</dt>
<dd>
`List[str]`
A list of field names to retrieve from the statistics table.
</dd>
<dt>geometry</dt>
<dd>
`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
</dd>
</dl>
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,
Expand All @@ -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
----------
<dl>
<dt>aoi</dt>
<dd>
`GeoJSON Feature`
The Area of Interest, either as a `Feature` or an instance of `AoiModel`
</dd>
<dt>spatial_join_method</dt>
<dd>
`["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
</dd>
<dt>fields</dt>
<dd>
`List[str]`
A list of field names to retrieve from the statistics table.
</dd>
<dt>aggregation_type</dt>
<dd>
`["sum", "avg", "count", "max", "min"]`
The manner in which to aggregate the statistics.
</dd>
</dl>
Returns
-------
`Dict[str, float]`
"""
try:
return table.aggregate(
aoi=body.aoi,
Expand All @@ -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():
Expand Down
24 changes: 21 additions & 3 deletions space2stats_api/src/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 104ad65

Please sign in to comment.