Skip to content

Commit ed9f205

Browse files
Dataset path params dependency (#260)
* rename and change type of PathParams * update changelog
1 parent f5f56d4 commit ed9f205

File tree

8 files changed

+107
-189
lines changed

8 files changed

+107
-189
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* renamed `OptionalHeaders`, `MimeTypes` and `ImageDrivers` enums to the singular form. (https://github.com/developmentseed/titiler/pull/258)
1313
* renamed `MimeType` to `MediaType` (https://github.com/developmentseed/titiler/pull/258)
1414
* add `ColorMapParams` dependency to ease the creation of custom colormap dependency (https://github.com/developmentseed/titiler/pull/252)
15+
* renamed `PathParams` to `DatasetPathParams` and also made it a simple callable (https://github.com/developmentseed/titiler/pull/260)
1516

1617
## 0.1.0 (2021-02-17)
1718

docs/concepts/customization.md

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class STACTiler(TilerFactory):
177177
stac = STACTiler(router_prefix="stac")
178178
```
179179

180-
### Custom PathParams for `path_dependency`
180+
### Custom DatasetPathParams for `path_dependency`
181181

182182
One common customization could be to create your own `path_dependency` (used in all endpoints).
183183

@@ -195,26 +195,18 @@ MOSAIC_BACKEND = os.getenv("TITILER_MOSAIC_BACKEND")
195195
MOSAIC_HOST = os.getenv("TITILER_MOSAIC_HOST")
196196

197197

198-
@dataclass
199-
class PathParams(DefaultDependency):
200-
"""Create dataset path from args"""
201-
198+
def MosaicPathParams(
202199
mosaic: str = Query(..., description="mosaic name")
200+
) -> str:
201+
"""Create dataset path from args"""
202+
# mosaic name should be in form of `{user}.{layername}`
203+
if not re.match(self.mosaic, r"^[a-zA-Z0-9-_]{1,32}\.[a-zA-Z0-9-_]{1,32}$"):
204+
raise HTTPException(
205+
status_code=400,
206+
detail=f"Invalid mosaic name {self.mosaic}.",
207+
)
203208

204-
# We need url to match default PathParams signature
205-
# Because we set `init=False` those params won't appear in OpenAPI docs.
206-
url: Optional[str] = field(init=False, default=None)
207-
208-
def __post_init__(self,):
209-
"""Define dataset URL."""
210-
# mosaic name should be in form of `{user}.{layername}`
211-
if not re.match(self.mosaic, r"^[a-zA-Z0-9-_]{1,32}\.[a-zA-Z0-9-_]{1,32}$"):
212-
raise HTTPException(
213-
status_code=400,
214-
detail=f"Invalid mosaic name {self.mosaic}.",
215-
)
216-
217-
self.url = f"{MOSAIC_BACKEND}{MOSAIC_HOST}/{self.mosaic}.json.gz"
209+
return f"{MOSAIC_BACKEND}{MOSAIC_HOST}/{self.mosaic}.json.gz"
218210
```
219211

220212
### Custom TMS
@@ -335,7 +327,7 @@ class CustomMosaicFactory(MosaicTilerFactory):
335327
src_path = self.path_dependency(body.url)
336328
with rasterio.Env(**self.gdal_config):
337329
with self.reader(
338-
src_path.url, mosaic_def=mosaic, reader=self.dataset_reader
330+
src_path, mosaic_def=mosaic, reader=self.dataset_reader
339331
) as mosaic:
340332
try:
341333
mosaic.write(overwrite=body.overwrite)
@@ -358,7 +350,7 @@ class CustomMosaicFactory(MosaicTilerFactory):
358350
"""Update an existing MosaicJSON"""
359351
src_path = self.path_dependency(body.url)
360352
with rasterio.Env(**self.gdal_config):
361-
with self.reader(src_path.url, reader=self.dataset_reader) as mosaic:
353+
with self.reader(src_path, reader=self.dataset_reader) as mosaic:
362354
features = get_footprints(body.files, max_threads=body.max_threads)
363355
try:
364356
mosaic.update(features, add_first=body.add_first, quiet=True)

docs/concepts/dependencies.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ The `factories` allow users to set multiple default dependencies. Here is the li
5858

5959
* **path_dependency**: Set dataset path (url).
6060
```python
61-
@dataclass
62-
class PathParams(DefaultDependency):
63-
"""Create dataset path from args"""
64-
61+
def DatasetPathParams(
6562
url: str = Query(..., description="Dataset URL")
63+
) -> str:
64+
"""Create dataset path from args"""
65+
return url
6666
```
6767

6868
* **tms_dependency**: The TMS dependency sets the available TMS for a tile endpoint.

docs/examples/Create_CustomSentinel2Tiler.ipynb

Lines changed: 11 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -68,137 +68,32 @@
6868
">>> pip install titiler rio-tiler-pds\n",
6969
"\n",
7070
"\"\"\"\n",
71-
"from dataclasses import dataclass, field\n",
72-
"from typing import Dict, Type, Optional, Sequence\n",
71+
"from titiler.endpoints.factory import MultiBandTilerFactory, MosaicTilerFactory\n",
72+
"from titiler.dependencies import BandsExprParams\n",
7373
"\n",
74-
"from titiler.endpoints.factory import TilerFactory, MosaicTilerFactory\n",
75-
"from titiler.dependencies import DefaultDependency\n",
76-
"\n",
77-
"\n",
78-
"from rio_tiler.models import Info, Metadata\n",
7974
"from rio_tiler_pds.sentinel.aws import S2COGReader\n",
8075
"from rio_tiler_pds.sentinel.utils import s2_sceneid_parser\n",
8176
"\n",
82-
"from fastapi import FastAPI, Depends, Query\n",
77+
"from fastapi import FastAPI, Query\n",
8378
"\n",
8479
"\n",
85-
"@dataclass\n",
86-
"class CustomPathParams:\n",
80+
"def CustomPathParams(\n",
81+
" sceneid: str = Query(..., description=\"Sentinel 2 Sceneid.\")\n",
82+
"):\n",
8783
" \"\"\"Create dataset path from args\"\"\"\n",
88-
"\n",
89-
" sceneid: str = Query(..., description=\"Landsat 8 Sceneid.\")\n",
90-
" scene_metadata: Dict = field(init=False)\n",
91-
"\n",
92-
" def __post_init__(self,):\n",
93-
" \"\"\"Define dataset URL.\"\"\"\n",
94-
" self.url = self.sceneid\n",
95-
" self.scene_metadata = s2_sceneid_parser(self.sceneid)\n",
96-
"\n",
97-
"\n",
98-
"def BandsParams(\n",
99-
" bands: str = Query(\n",
100-
" ...,\n",
101-
" title=\"bands names\",\n",
102-
" description=\"comma (',') delimited bands names.\",\n",
103-
" )\n",
104-
") -> Sequence[str]:\n",
105-
" \"\"\"Bands.\"\"\"\n",
106-
" return bands.split(\",\")\n",
107-
"\n",
108-
"\n",
109-
"@dataclass\n",
110-
"class BandsExprParams(DefaultDependency):\n",
111-
" \"\"\"Band names and Expression parameters.\"\"\"\n",
112-
"\n",
113-
" bands: Optional[str] = Query(\n",
114-
" None,\n",
115-
" title=\"bands names\",\n",
116-
" description=\"comma (',') delimited bands names.\",\n",
117-
" )\n",
118-
" expression: Optional[str] = Query(\n",
119-
" None,\n",
120-
" title=\"Band Math expression\",\n",
121-
" description=\"rio-tiler's band math expression.\",\n",
122-
" )\n",
123-
"\n",
124-
" def __post_init__(self):\n",
125-
" \"\"\"Post Init.\"\"\"\n",
126-
" if self.bands is not None:\n",
127-
" self.kwargs[\"bands\"] = self.bands.split(\",\")\n",
128-
" if self.expression is not None:\n",
129-
" self.kwargs[\"expression\"] = self.expression\n",
130-
"\n",
131-
"\n",
132-
"@dataclass\n",
133-
"class S2COGTiler(TilerFactory):\n",
134-
" \"\"\"Custom Tiler Class for STAC.\"\"\"\n",
135-
"\n",
136-
" reader: Type[S2COGReader] = field(default=S2COGReader)\n",
137-
"\n",
138-
" path_dependency: Type[CustomPathParams] = CustomPathParams\n",
139-
"\n",
140-
" layer_dependency: Type[DefaultDependency] = BandsExprParams\n",
141-
"\n",
142-
" def info(self):\n",
143-
" \"\"\"Register /info endpoint.\"\"\"\n",
144-
"\n",
145-
" @self.router.get(\n",
146-
" \"/info\",\n",
147-
" response_model=Info,\n",
148-
" response_model_exclude={\"minzoom\", \"maxzoom\", \"center\"},\n",
149-
" response_model_exclude_none=True,\n",
150-
" responses={200: {\"description\": \"Return dataset's basic info.\"}},\n",
151-
" )\n",
152-
" def info(\n",
153-
" src_path=Depends(self.path_dependency),\n",
154-
" bands=Depends(BandsParams),\n",
155-
" kwargs: Dict = Depends(self.additional_dependency),\n",
156-
" ):\n",
157-
" \"\"\"Return basic info.\"\"\"\n",
158-
" with self.reader(src_path.url, **self.reader_options) as src_dst:\n",
159-
" info = src_dst.info(bands=bands, **kwargs)\n",
160-
" return info\n",
161-
"\n",
162-
" def metadata(self):\n",
163-
" \"\"\"Register /metadata endpoint.\"\"\"\n",
164-
"\n",
165-
" @self.router.get(\n",
166-
" \"/metadata\",\n",
167-
" response_model=Metadata,\n",
168-
" response_model_exclude={\"minzoom\", \"maxzoom\", \"center\"},\n",
169-
" response_model_exclude_none=True,\n",
170-
" responses={200: {\"description\": \"Return dataset's metadata.\"}},\n",
171-
" )\n",
172-
" def metadata(\n",
173-
" src_path=Depends(self.path_dependency),\n",
174-
" bands=Depends(BandsParams),\n",
175-
" metadata_params=Depends(self.metadata_dependency),\n",
176-
" kwargs: Dict = Depends(self.additional_dependency),\n",
177-
" ):\n",
178-
" \"\"\"Return metadata.\"\"\"\n",
179-
" with self.reader(src_path.url, **self.reader_options) as src_dst:\n",
180-
" info = src_dst.metadata(\n",
181-
" metadata_params.pmin,\n",
182-
" metadata_params.pmax,\n",
183-
" bands=bands,\n",
184-
" **metadata_params.kwargs,\n",
185-
" **kwargs,\n",
186-
" )\n",
187-
" return info\n",
84+
" assert s2_sceneid_parser(sceneid)\n",
85+
" return sceneid\n",
18886
"\n",
18987
"\n",
19088
"app = FastAPI()\n",
19189
"\n",
192-
"\n",
193-
"scene_tiler = S2COGTiler(router_prefix=\"scenes\")\n",
90+
"scene_tiler = MultiBandTilerFactory(reader=S2COGReader, path_dependency=CustomPathParams, router_prefix=\"scenes\")\n",
19491
"app.include_router(scene_tiler.router, prefix=\"/scenes\", tags=[\"scenes\"])\n",
19592
"\n",
19693
"mosaic_tiler = MosaicTilerFactory(\n",
19794
" router_prefix=\"mosaic\",\n",
19895
" dataset_reader=S2COGReader,\n",
19996
" layer_dependency=BandsExprParams,\n",
200-
" add_update=False,\n",
201-
" add_create=False,\n",
20297
")\n",
20398
"app.include_router(mosaic_tiler.router, prefix=\"/mosaic\", tags=[\"mosaic\"])\n",
20499
"```"
@@ -704,9 +599,9 @@
704599
"name": "python",
705600
"nbconvert_exporter": "python",
706601
"pygments_lexer": "ipython3",
707-
"version": "3.8.6"
602+
"version": "3.8.2-final"
708603
}
709604
},
710605
"nbformat": 4,
711606
"nbformat_minor": 2
712-
}
607+
}

tests/test_CustomPath.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Test TiTiler Custom Path Params."""
2+
3+
import re
4+
5+
from titiler.endpoints import factory
6+
7+
from .conftest import DATA_DIR
8+
9+
from fastapi import FastAPI, HTTPException, Query
10+
11+
from starlette.testclient import TestClient
12+
13+
14+
def CustomPathParams(url: str = Query(..., description="Give me a url.",)) -> str:
15+
"""Custom path Dependency."""
16+
if not re.match("^c.+tif$", url):
17+
raise HTTPException(
18+
status_code=400, detail="Nope, this is not a valid URL - Please Try Again",
19+
)
20+
return f"{DATA_DIR}/{url}"
21+
22+
23+
def test_CustomPath():
24+
"""Test Custom Render Params dependency."""
25+
app = FastAPI()
26+
27+
cog = factory.TilerFactory(path_dependency=CustomPathParams)
28+
app.include_router(cog.router)
29+
client = TestClient(app)
30+
31+
response = client.get("/preview.png?url=cog.tif&rescale=0,10000")
32+
assert response.status_code == 200
33+
assert response.headers["content-type"] == "image/png"
34+
35+
response = client.get("/preview.png?url=somethingelse.tif&rescale=0,10000")
36+
assert response.status_code == 400

titiler/dependencies.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,18 @@ def ColorMapParams(
7777
return None
7878

7979

80+
def DatasetPathParams(url: str = Query(..., description="Dataset URL")) -> str:
81+
"""Create dataset path from args"""
82+
return url
83+
84+
8085
@dataclass
8186
class DefaultDependency:
8287
"""Dependency Base Class"""
8388

8489
kwargs: Dict = field(init=False, default_factory=dict)
8590

8691

87-
@dataclass
88-
class PathParams:
89-
"""Create dataset path from args"""
90-
91-
url: str = Query(..., description="Dataset URL")
92-
93-
9492
# Dependencies for simple BaseReader (e.g COGReader)
9593
@dataclass
9694
class BidxParams(DefaultDependency):

titiler/endpoints/cog.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from rio_cogeo.cogeo import cog_info as rio_cogeo_info
44
from rio_tiler.io import COGReader
55

6-
from ..dependencies import PathParams
6+
from ..dependencies import DatasetPathParams
77
from ..models.cogeo import Info
88
from ..templates import templates
99
from .factory import TilerFactory
@@ -19,11 +19,11 @@
1919

2020
@cog.router.get("/validate", response_model=Info)
2121
def cog_validate(
22-
src_path: PathParams = Depends(),
22+
src_path: str = Depends(DatasetPathParams),
2323
strict: bool = Query(False, description="Treat warnings as errors"),
2424
):
2525
"""Validate a COG"""
26-
return rio_cogeo_info(src_path.url, strict=strict)
26+
return rio_cogeo_info(src_path, strict=strict)
2727

2828

2929
@cog.router.get("/viewer", response_class=HTMLResponse)

0 commit comments

Comments
 (0)