Skip to content

Commit

Permalink
make timings and metadata headers optional (#232)
Browse files Browse the repository at this point in the history
* make timings and metadata headers optional

* add add_assets_in_headers to add X-Assets in response headers

* do what kylebarron tells me to do

* update changes
  • Loading branch information
vincentsarago authored Feb 17, 2021
1 parent a95073d commit d93b14a
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* add `MultiBaseTilerFactory` and `MultiBandTilerFactory` custom tiler factories (https://github.com/developmentseed/titiler/pull/230)
* Update STAC tiler to use the new `MultiBaseTilerFactory` factory
* depreciate *empty* GET endpoint for MosaicTilerFactory read (https://github.com/developmentseed/titiler/pull/232)
* better `debug` configuration and make reponse headers metadata optional (https://github.com/developmentseed/titiler/pull/232)

**breaking change**

Expand Down
14 changes: 0 additions & 14 deletions tests/routes/test_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,6 @@ def test_tile(rio, app):
meta = parse_img(response.content)
assert meta["width"] == 256
assert meta["height"] == 256
timing = response.headers["server-timing"]
assert "dataread;dur" in timing
assert "postprocess;dur" in timing
assert "format;dur" in timing

response = app.get(
"/cog/tiles/8/87/48@2x?url=https://myurl.com/cog.tif&rescale=0,1000&color_formula=Gamma R 3"
Expand Down Expand Up @@ -278,10 +274,6 @@ def test_preview(rio, app):
assert meta["width"] == 256
assert meta["height"] == 256
assert meta["driver"] == "JPEG"
timing = response.headers["server-timing"]
assert "dataread;dur" in timing
assert "postprocess;dur" in timing
assert "format;dur" in timing

response = app.get(
"/cog/preview.png?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256"
Expand Down Expand Up @@ -339,10 +331,6 @@ def test_part(rio, app):
assert meta["width"] == 256
assert meta["height"] == 73
assert meta["driver"] == "PNG"
timing = response.headers["server-timing"]
assert "dataread;dur" in timing
assert "postprocess;dur" in timing
assert "format;dur" in timing

response = app.get(
"/cog/crop/-56.228,72.715,-54.547,73.188.jpg?url=https://myurl.com/cog.tif&rescale=0,1000&max_size=256&return_mask=false"
Expand Down Expand Up @@ -394,8 +382,6 @@ def test_point(rio, app):
assert response.headers["content-type"] == "application/json"
body = response.json()
assert body["coordinates"] == [-56.228, 72.715]
timing = response.headers["server-timing"]
assert "dataread;dur" in timing


def test_file_not_found_error(app):
Expand Down
11 changes: 0 additions & 11 deletions tests/routes/test_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ def test_point(app):
assert len(body["values"]) == 1
assert body["values"][0][0].endswith(".tif")
assert body["values"][0][1] == [9943, 9127, 9603]
timing = response.headers["server-timing"]
assert "mosaicread;dur" in timing
assert "dataread;dur" in timing
assert "total;dur" in timing


def test_tile(app):
Expand All @@ -126,15 +122,8 @@ def test_tile(app):
)
assert response.status_code == 200
assert response.headers["content-type"] == "image/png"
assert response.headers["X-Assets"]
meta = parse_img(response.content)
assert meta["width"] == meta["height"] == 256
timing = response.headers["server-timing"]
assert "mosaicread;dur" in timing
assert "dataread;dur" in timing
assert "postprocess;dur" in timing
assert "format;dur" in timing
assert "total;dur" in timing

response = app.get(
f"/mosaicjson/tiles/{tile.z}/{tile.x}/{tile.y}@2x",
Expand Down
121 changes: 106 additions & 15 deletions tests/test_factories.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,117 @@
# """Test TiTiler Tiler Factories."""

from rio_tiler.io import COGReader
import os
import tempfile
from contextlib import contextmanager

from cogeo_mosaic.backends import FileBackend
from cogeo_mosaic.mosaic import MosaicJSON

def test_TilerFactory(set_env):
from titiler.dependencies import TMSParams, WebMercatorTMSParams
from titiler.endpoints import factory
from titiler.resources.enums import OptionalHeaders

from .conftest import DATA_DIR

from fastapi import FastAPI

from starlette.testclient import TestClient

assets = [os.path.join(DATA_DIR, asset) for asset in ["cog1.tif", "cog2.tif"]]


def test_TilerFactory():
"""Test TilerFactory class."""
from titiler.dependencies import TMSParams
from titiler.endpoints import factory
cog = factory.TilerFactory()
assert len(cog.router.routes) == 21
assert cog.tms_dependency == TMSParams

cog = factory.TilerFactory(add_preview=False, add_part=False)
assert len(cog.router.routes) == 17

app = FastAPI()
cog = factory.TilerFactory(optional_headers=[OptionalHeaders.server_timing])
app.include_router(cog.router)
client = TestClient(app)

app = factory.TilerFactory(reader=COGReader)
assert len(app.router.routes) == 21
assert app.tms_dependency == TMSParams
response = client.get(f"/tiles/8/87/48?url={DATA_DIR}/cog.tif&rescale=0,1000")
assert response.status_code == 200
assert response.headers["content-type"] == "image/jpeg"
timing = response.headers["server-timing"]
assert "dataread;dur" in timing
assert "postprocess;dur" in timing
assert "format;dur" in timing

app = factory.TilerFactory(reader=COGReader, add_preview=False, add_part=False)
assert len(app.router.routes) == 17
response = client.get(
f"/preview?url={DATA_DIR}/cog.tif&rescale=0,1000&max_size=256"
)
assert response.status_code == 200
assert response.headers["content-type"] == "image/jpeg"
timing = response.headers["server-timing"]
assert "dataread;dur" in timing
assert "postprocess;dur" in timing
assert "format;dur" in timing

response = client.get(
f"/crop/-56.228,72.715,-54.547,73.188.png?url={DATA_DIR}/cog.tif&rescale=0,1000&max_size=256"
)
assert response.status_code == 200
assert response.headers["content-type"] == "image/png"
timing = response.headers["server-timing"]
assert "dataread;dur" in timing
assert "postprocess;dur" in timing
assert "format;dur" in timing

def test_MosaicTilerFactory(set_env):
response = client.get(f"/point/-56.228,72.715?url={DATA_DIR}/cog.tif")
assert response.status_code == 200
assert response.headers["content-type"] == "application/json"
timing = response.headers["server-timing"]
assert "dataread;dur" in timing


@contextmanager
def tmpmosaic():
"""Create a Temporary MosaicJSON file."""
fileobj = tempfile.NamedTemporaryFile(suffix=".json.gz", delete=False)
fileobj.close()
mosaic_def = MosaicJSON.from_urls(assets)
with FileBackend(fileobj.name, mosaic_def=mosaic_def) as mosaic:
mosaic.write(overwrite=True)

try:
yield fileobj.name
finally:
os.remove(fileobj.name)


def test_MosaicTilerFactory():
"""Test MosaicTilerFactory class."""
from titiler.dependencies import WebMercatorTMSParams
from titiler.endpoints import factory
mosaic = factory.MosaicTilerFactory(
optional_headers=[OptionalHeaders.server_timing, OptionalHeaders.x_assets],
router_prefix="mosaic",
)
assert len(mosaic.router.routes) == 19
assert mosaic.tms_dependency == WebMercatorTMSParams

app = FastAPI()
app.include_router(mosaic.router, prefix="/mosaic")
client = TestClient(app)

with tmpmosaic() as mosaic_file:
response = client.get(
"/mosaic/point/-74.53125,45.9956935", params={"url": mosaic_file},
)
assert response.status_code == 200
timing = response.headers["server-timing"]
assert "mosaicread;dur" in timing
assert "dataread;dur" in timing

response = client.get("/mosaic/tiles/7/37/45", params={"url": mosaic_file})
assert response.status_code == 200

app = factory.MosaicTilerFactory()
assert len(app.router.routes) == 19
assert app.tms_dependency == WebMercatorTMSParams
assert response.headers["X-Assets"]
timing = response.headers["server-timing"]
assert "mosaicread;dur" in timing
assert "dataread;dur" in timing
assert "postprocess;dur" in timing
assert "format;dur" in timing
1 change: 0 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ def test_health(app):
response = app.get("/healthz")
assert response.status_code == 200
assert response.json() == {"ping": "pong!"}
assert response.headers["server-timing"]
48 changes: 32 additions & 16 deletions titiler/endpoints/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@
)
from ..models.mapbox import TileJSON
from ..models.OGC import TileMatrixSetList
from ..resources.enums import ImageType, MimeTypes, PixelSelectionMethod
from ..resources.enums import (
ImageType,
MimeTypes,
OptionalHeaders,
PixelSelectionMethod,
)
from ..resources.responses import GeoJSONResponse, XMLResponse
from ..templates import templates

Expand Down Expand Up @@ -100,6 +105,9 @@ class BaseTilerFactory(metaclass=abc.ABCMeta):
# Add specific GDAL environement (e.g {"AWS_REQUEST_PAYER": "requester"})
gdal_config: Dict = field(default_factory=dict)

# add additional headers in response
optional_headers: List[OptionalHeaders] = field(default_factory=list)

def __post_init__(self):
"""Post Init: register route and configure specific options."""
self.register_routes()
Expand Down Expand Up @@ -341,9 +349,10 @@ def tile(
)
timings.append(("format", round(t.elapsed * 1000, 2)))

headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)
if OptionalHeaders.server_timing in self.optional_headers:
headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)

return Response(content, media_type=format.mimetype, headers=headers)

Expand Down Expand Up @@ -544,9 +553,10 @@ def point(
)
timings.append(("dataread", round(t.elapsed * 1000, 2)))

response.headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)
if OptionalHeaders.server_timing in self.optional_headers:
response.headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)

return {"coordinates": [lon, lat], "values": values}

Expand Down Expand Up @@ -606,7 +616,7 @@ def preview(
)
timings.append(("format", round(t.elapsed * 1000, 2)))

if timings:
if OptionalHeaders.server_timing in self.optional_headers:
headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)
Expand Down Expand Up @@ -674,7 +684,7 @@ def part(
)
timings.append(("format", round(t.elapsed * 1000, 2)))

if timings:
if OptionalHeaders.server_timing in self.optional_headers:
headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)
Expand Down Expand Up @@ -934,6 +944,9 @@ class MosaicTilerFactory(BaseTilerFactory):
# BaseBackend does not support other TMS than WebMercator
tms_dependency: Callable[..., TileMatrixSet] = WebMercatorTMSParams

# Add X-Assets in response headers
add_assets_headers: bool = False

def register_routes(self):
"""
This Method register routes to the router.
Expand Down Expand Up @@ -1127,11 +1140,13 @@ def tile(
)
timings.append(("format", round(t.elapsed * 1000, 2)))

headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)
if OptionalHeaders.server_timing in self.optional_headers:
headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)

headers["X-Assets"] = ",".join(data.assets)
if OptionalHeaders.x_assets in self.optional_headers:
headers["X-Assets"] = ",".join(data.assets)

return Response(content, media_type=format.mimetype, headers=headers)

Expand Down Expand Up @@ -1340,9 +1355,10 @@ def point(
)
timings.append(("dataread", round((t.elapsed - mosaic_read) * 1000, 2)))

response.headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)
if OptionalHeaders.server_timing in self.optional_headers:
response.headers["Server-Timing"] = ", ".join(
[f"{name};dur={time}" for (name, time) in timings]
)

return {"coordinates": [lon, lat], "values": values}

Expand Down
4 changes: 2 additions & 2 deletions titiler/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@

app.add_middleware(BrotliMiddleware, minimum_size=0, gzip_fallback=True)
app.add_middleware(CacheControlMiddleware, cachecontrol=api_settings.cachecontrol)
app.add_middleware(TotalTimeMiddleware)
if api_settings.debug:
app.add_middleware(LoggerMiddleware)
app.add_middleware(TotalTimeMiddleware)
app.add_middleware(LoggerMiddleware, headers=True, querystrings=True)


@app.get("/healthz", description="Health Check", tags=["Health Check"])
Expand Down
7 changes: 7 additions & 0 deletions titiler/resources/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,10 @@ class PixelSelectionMethod(str, Enum):
def method(self):
"""Return rio-tiler-mosaic pixel selection class"""
return getattr(defaults, f"{self._value_.title()}Method")


class OptionalHeaders(str, Enum):
"""Optional Headers in responses."""

server_timing = "Server-Timing"
x_assets = "X-Assets"

0 comments on commit d93b14a

Please sign in to comment.