Skip to content

Commit d93b14a

Browse files
make timings and metadata headers optional (#232)
* 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
1 parent a95073d commit d93b14a

File tree

8 files changed

+148
-59
lines changed

8 files changed

+148
-59
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* add `MultiBaseTilerFactory` and `MultiBandTilerFactory` custom tiler factories (https://github.com/developmentseed/titiler/pull/230)
1515
* Update STAC tiler to use the new `MultiBaseTilerFactory` factory
1616
* depreciate *empty* GET endpoint for MosaicTilerFactory read (https://github.com/developmentseed/titiler/pull/232)
17+
* better `debug` configuration and make reponse headers metadata optional (https://github.com/developmentseed/titiler/pull/232)
1718

1819
**breaking change**
1920

tests/routes/test_cog.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,6 @@ def test_tile(rio, app):
129129
meta = parse_img(response.content)
130130
assert meta["width"] == 256
131131
assert meta["height"] == 256
132-
timing = response.headers["server-timing"]
133-
assert "dataread;dur" in timing
134-
assert "postprocess;dur" in timing
135-
assert "format;dur" in timing
136132

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

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

347335
response = app.get(
348336
"/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"
@@ -394,8 +382,6 @@ def test_point(rio, app):
394382
assert response.headers["content-type"] == "application/json"
395383
body = response.json()
396384
assert body["coordinates"] == [-56.228, 72.715]
397-
timing = response.headers["server-timing"]
398-
assert "dataread;dur" in timing
399385

400386

401387
def test_file_not_found_error(app):

tests/routes/test_mosaic.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,6 @@ def test_point(app):
105105
assert len(body["values"]) == 1
106106
assert body["values"][0][0].endswith(".tif")
107107
assert body["values"][0][1] == [9943, 9127, 9603]
108-
timing = response.headers["server-timing"]
109-
assert "mosaicread;dur" in timing
110-
assert "dataread;dur" in timing
111-
assert "total;dur" in timing
112108

113109

114110
def test_tile(app):
@@ -126,15 +122,8 @@ def test_tile(app):
126122
)
127123
assert response.status_code == 200
128124
assert response.headers["content-type"] == "image/png"
129-
assert response.headers["X-Assets"]
130125
meta = parse_img(response.content)
131126
assert meta["width"] == meta["height"] == 256
132-
timing = response.headers["server-timing"]
133-
assert "mosaicread;dur" in timing
134-
assert "dataread;dur" in timing
135-
assert "postprocess;dur" in timing
136-
assert "format;dur" in timing
137-
assert "total;dur" in timing
138127

139128
response = app.get(
140129
f"/mosaicjson/tiles/{tile.z}/{tile.x}/{tile.y}@2x",

tests/test_factories.py

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,117 @@
11
# """Test TiTiler Tiler Factories."""
22

3-
from rio_tiler.io import COGReader
3+
import os
4+
import tempfile
5+
from contextlib import contextmanager
46

7+
from cogeo_mosaic.backends import FileBackend
8+
from cogeo_mosaic.mosaic import MosaicJSON
59

6-
def test_TilerFactory(set_env):
10+
from titiler.dependencies import TMSParams, WebMercatorTMSParams
11+
from titiler.endpoints import factory
12+
from titiler.resources.enums import OptionalHeaders
13+
14+
from .conftest import DATA_DIR
15+
16+
from fastapi import FastAPI
17+
18+
from starlette.testclient import TestClient
19+
20+
assets = [os.path.join(DATA_DIR, asset) for asset in ["cog1.tif", "cog2.tif"]]
21+
22+
23+
def test_TilerFactory():
724
"""Test TilerFactory class."""
8-
from titiler.dependencies import TMSParams
9-
from titiler.endpoints import factory
25+
cog = factory.TilerFactory()
26+
assert len(cog.router.routes) == 21
27+
assert cog.tms_dependency == TMSParams
28+
29+
cog = factory.TilerFactory(add_preview=False, add_part=False)
30+
assert len(cog.router.routes) == 17
31+
32+
app = FastAPI()
33+
cog = factory.TilerFactory(optional_headers=[OptionalHeaders.server_timing])
34+
app.include_router(cog.router)
35+
client = TestClient(app)
1036

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

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

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

19-
def test_MosaicTilerFactory(set_env):
65+
response = client.get(f"/point/-56.228,72.715?url={DATA_DIR}/cog.tif")
66+
assert response.status_code == 200
67+
assert response.headers["content-type"] == "application/json"
68+
timing = response.headers["server-timing"]
69+
assert "dataread;dur" in timing
70+
71+
72+
@contextmanager
73+
def tmpmosaic():
74+
"""Create a Temporary MosaicJSON file."""
75+
fileobj = tempfile.NamedTemporaryFile(suffix=".json.gz", delete=False)
76+
fileobj.close()
77+
mosaic_def = MosaicJSON.from_urls(assets)
78+
with FileBackend(fileobj.name, mosaic_def=mosaic_def) as mosaic:
79+
mosaic.write(overwrite=True)
80+
81+
try:
82+
yield fileobj.name
83+
finally:
84+
os.remove(fileobj.name)
85+
86+
87+
def test_MosaicTilerFactory():
2088
"""Test MosaicTilerFactory class."""
21-
from titiler.dependencies import WebMercatorTMSParams
22-
from titiler.endpoints import factory
89+
mosaic = factory.MosaicTilerFactory(
90+
optional_headers=[OptionalHeaders.server_timing, OptionalHeaders.x_assets],
91+
router_prefix="mosaic",
92+
)
93+
assert len(mosaic.router.routes) == 19
94+
assert mosaic.tms_dependency == WebMercatorTMSParams
95+
96+
app = FastAPI()
97+
app.include_router(mosaic.router, prefix="/mosaic")
98+
client = TestClient(app)
99+
100+
with tmpmosaic() as mosaic_file:
101+
response = client.get(
102+
"/mosaic/point/-74.53125,45.9956935", params={"url": mosaic_file},
103+
)
104+
assert response.status_code == 200
105+
timing = response.headers["server-timing"]
106+
assert "mosaicread;dur" in timing
107+
assert "dataread;dur" in timing
108+
109+
response = client.get("/mosaic/tiles/7/37/45", params={"url": mosaic_file})
110+
assert response.status_code == 200
23111

24-
app = factory.MosaicTilerFactory()
25-
assert len(app.router.routes) == 19
26-
assert app.tms_dependency == WebMercatorTMSParams
112+
assert response.headers["X-Assets"]
113+
timing = response.headers["server-timing"]
114+
assert "mosaicread;dur" in timing
115+
assert "dataread;dur" in timing
116+
assert "postprocess;dur" in timing
117+
assert "format;dur" in timing

tests/test_main.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,3 @@ def test_health(app):
66
response = app.get("/healthz")
77
assert response.status_code == 200
88
assert response.json() == {"ping": "pong!"}
9-
assert response.headers["server-timing"]

titiler/endpoints/factory.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@
3636
)
3737
from ..models.mapbox import TileJSON
3838
from ..models.OGC import TileMatrixSetList
39-
from ..resources.enums import ImageType, MimeTypes, PixelSelectionMethod
39+
from ..resources.enums import (
40+
ImageType,
41+
MimeTypes,
42+
OptionalHeaders,
43+
PixelSelectionMethod,
44+
)
4045
from ..resources.responses import GeoJSONResponse, XMLResponse
4146
from ..templates import templates
4247

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

108+
# add additional headers in response
109+
optional_headers: List[OptionalHeaders] = field(default_factory=list)
110+
103111
def __post_init__(self):
104112
"""Post Init: register route and configure specific options."""
105113
self.register_routes()
@@ -341,9 +349,10 @@ def tile(
341349
)
342350
timings.append(("format", round(t.elapsed * 1000, 2)))
343351

344-
headers["Server-Timing"] = ", ".join(
345-
[f"{name};dur={time}" for (name, time) in timings]
346-
)
352+
if OptionalHeaders.server_timing in self.optional_headers:
353+
headers["Server-Timing"] = ", ".join(
354+
[f"{name};dur={time}" for (name, time) in timings]
355+
)
347356

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

@@ -544,9 +553,10 @@ def point(
544553
)
545554
timings.append(("dataread", round(t.elapsed * 1000, 2)))
546555

547-
response.headers["Server-Timing"] = ", ".join(
548-
[f"{name};dur={time}" for (name, time) in timings]
549-
)
556+
if OptionalHeaders.server_timing in self.optional_headers:
557+
response.headers["Server-Timing"] = ", ".join(
558+
[f"{name};dur={time}" for (name, time) in timings]
559+
)
550560

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

@@ -606,7 +616,7 @@ def preview(
606616
)
607617
timings.append(("format", round(t.elapsed * 1000, 2)))
608618

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

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

947+
# Add X-Assets in response headers
948+
add_assets_headers: bool = False
949+
937950
def register_routes(self):
938951
"""
939952
This Method register routes to the router.
@@ -1127,11 +1140,13 @@ def tile(
11271140
)
11281141
timings.append(("format", round(t.elapsed * 1000, 2)))
11291142

1130-
headers["Server-Timing"] = ", ".join(
1131-
[f"{name};dur={time}" for (name, time) in timings]
1132-
)
1143+
if OptionalHeaders.server_timing in self.optional_headers:
1144+
headers["Server-Timing"] = ", ".join(
1145+
[f"{name};dur={time}" for (name, time) in timings]
1146+
)
11331147

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

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

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

1343-
response.headers["Server-Timing"] = ", ".join(
1344-
[f"{name};dur={time}" for (name, time) in timings]
1345-
)
1358+
if OptionalHeaders.server_timing in self.optional_headers:
1359+
response.headers["Server-Timing"] = ", ".join(
1360+
[f"{name};dur={time}" for (name, time) in timings]
1361+
)
13461362

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

titiler/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656

5757
app.add_middleware(BrotliMiddleware, minimum_size=0, gzip_fallback=True)
5858
app.add_middleware(CacheControlMiddleware, cachecontrol=api_settings.cachecontrol)
59-
app.add_middleware(TotalTimeMiddleware)
6059
if api_settings.debug:
61-
app.add_middleware(LoggerMiddleware)
60+
app.add_middleware(TotalTimeMiddleware)
61+
app.add_middleware(LoggerMiddleware, headers=True, querystrings=True)
6262

6363

6464
@app.get("/healthz", description="Health Check", tags=["Health Check"])

titiler/resources/enums.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,10 @@ class PixelSelectionMethod(str, Enum):
7777
def method(self):
7878
"""Return rio-tiler-mosaic pixel selection class"""
7979
return getattr(defaults, f"{self._value_.title()}Method")
80+
81+
82+
class OptionalHeaders(str, Enum):
83+
"""Optional Headers in responses."""
84+
85+
server_timing = "Server-Timing"
86+
x_assets = "X-Assets"

0 commit comments

Comments
 (0)