Skip to content

Commit ca2c818

Browse files
create MosaicJSON extension for / and /validate (#1257)
1 parent 045961f commit ca2c818

7 files changed

Lines changed: 245 additions & 203 deletions

File tree

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
* add `FsReader` which use `fs_open_dataset` as `dataset_opener`
2121
* create offical application `titiler.xarray.main:app`
2222

23+
### titiler.mosaic
24+
25+
* move `/` and `/validate` to a `MosaicJSONExtension`
26+
2327
## 0.24.2 (2025-10-16)
2428

2529
### titiler.core

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ docs = [
7171
"griffe-inherited-docstrings>=1.0.0",
7272
"mkdocstrings[python]>=0.25.1",
7373
]
74+
server = [
75+
"uvicorn",
76+
]
7477

7578
[project.urls]
7679
Homepage = 'https://developmentseed.org/titiler/'

src/titiler/application/titiler/application/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
stacViewerExtension,
4343
)
4444
from titiler.mosaic.errors import MOSAIC_STATUS_CODES
45+
from titiler.mosaic.extensions import MosaicJSONExtension
4546
from titiler.mosaic.factory import MosaicTilerFactory
4647

4748
logging.getLogger("botocore.credentials").disabled = True
@@ -170,6 +171,9 @@ def validate_access_token(access_token: str = Security(api_key_query)):
170171
if not api_settings.disable_mosaic:
171172
mosaic = MosaicTilerFactory(
172173
router_prefix="/mosaicjson",
174+
extensions=[
175+
MosaicJSONExtension(),
176+
],
173177
enable_telemetry=api_settings.telemetry_enabled,
174178
templates=titiler_templates,
175179
)

src/titiler/mosaic/tests/test_factory.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from titiler.core.dependencies import DefaultDependency
2222
from titiler.core.resources.enums import OptionalHeader
23+
from titiler.mosaic.extensions import MosaicJSONExtension
2324
from titiler.mosaic.factory import MosaicTilerFactory
2425

2526
from .conftest import DATA_DIR
@@ -50,6 +51,15 @@ def test_MosaicTilerFactory():
5051
optional_headers=[OptionalHeader.x_assets],
5152
router_prefix="mosaic",
5253
)
54+
assert len(mosaic.router.routes) == 15
55+
56+
mosaic = MosaicTilerFactory(
57+
optional_headers=[OptionalHeader.x_assets],
58+
extensions=[
59+
MosaicJSONExtension(),
60+
],
61+
router_prefix="mosaic",
62+
)
5363
assert len(mosaic.router.routes) == 17
5464

5565
app = FastAPI()
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""titiler.mosaic extensions."""
2+
3+
import logging
4+
from dataclasses import dataclass
5+
6+
import rasterio
7+
from cogeo_mosaic.mosaic import MosaicJSON
8+
from fastapi import Depends
9+
10+
from titiler.core.factory import FactoryExtension
11+
from titiler.mosaic.factory import MosaicTilerFactory
12+
13+
logger = logging.getLogger(__name__)
14+
15+
16+
@dataclass
17+
class MosaicJSONExtension(FactoryExtension):
18+
"""Add MosaicJSON specific endpoints"""
19+
20+
def register(self, factory: MosaicTilerFactory): # noqa: C901
21+
"""Register endpoint to the tiler factory."""
22+
23+
############################################################################
24+
# /read
25+
############################################################################
26+
27+
@factory.router.get(
28+
"/",
29+
response_model=MosaicJSON,
30+
response_model_exclude_none=True,
31+
responses={200: {"description": "Return MosaicJSON definition"}},
32+
operation_id=f"{factory.operation_prefix}getMosaicJSON",
33+
)
34+
def read(
35+
src_path=Depends(factory.path_dependency),
36+
backend_params=Depends(factory.backend_dependency),
37+
reader_params=Depends(factory.reader_dependency),
38+
env=Depends(factory.environment_dependency),
39+
):
40+
"""Read a MosaicJSON"""
41+
with rasterio.Env(**env):
42+
logger.info(
43+
f"opening data with backend: {factory.backend} and reader {factory.dataset_reader}"
44+
)
45+
with factory.backend(
46+
src_path,
47+
reader=factory.dataset_reader,
48+
reader_options=reader_params.as_dict(),
49+
**backend_params.as_dict(),
50+
) as src_dst:
51+
return src_dst.mosaic_def
52+
53+
@factory.router.post(
54+
"/validate",
55+
operation_id=f"{factory.operation_prefix}validate",
56+
)
57+
def validate(body: MosaicJSON):
58+
"""Validate a MosaicJSON"""
59+
return True

src/titiler/mosaic/titiler/mosaic/factory.py

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from attrs import define, field
1010
from cogeo_mosaic.backends import BaseBackend, MosaicBackend
1111
from cogeo_mosaic.models import Info as mosaicInfo
12-
from cogeo_mosaic.mosaic import MosaicJSON
1312
from fastapi import Depends, HTTPException, Path, Query
1413
from geojson_pydantic.features import Feature
1514
from geojson_pydantic.geometries import Polygon
@@ -143,7 +142,6 @@ class MosaicTilerFactory(BaseFactory):
143142
def register_routes(self):
144143
"""This Method register routes to the router."""
145144

146-
self.read()
147145
self.info()
148146
self.tilesets()
149147
self.tile()
@@ -152,41 +150,8 @@ def register_routes(self):
152150
self.tilejson()
153151
self.wmts()
154152
self.point()
155-
self.validate()
156153
self.assets()
157154

158-
############################################################################
159-
# /read
160-
############################################################################
161-
def read(self):
162-
"""Register / (Get) Read endpoint."""
163-
164-
@self.router.get(
165-
"/",
166-
response_model=MosaicJSON,
167-
response_model_exclude_none=True,
168-
responses={200: {"description": "Return MosaicJSON definition"}},
169-
operation_id=f"{self.operation_prefix}getMosaicJSON",
170-
)
171-
def read(
172-
src_path=Depends(self.path_dependency),
173-
backend_params=Depends(self.backend_dependency),
174-
reader_params=Depends(self.reader_dependency),
175-
env=Depends(self.environment_dependency),
176-
):
177-
"""Read a MosaicJSON"""
178-
with rasterio.Env(**env):
179-
logger.info(
180-
f"opening data with backend: {self.backend} and reader {self.dataset_reader}"
181-
)
182-
with self.backend(
183-
src_path,
184-
reader=self.dataset_reader,
185-
reader_options=reader_params.as_dict(),
186-
**backend_params.as_dict(),
187-
) as src_dst:
188-
return src_dst.mosaic_def
189-
190155
############################################################################
191156
# /info
192157
############################################################################
@@ -1021,17 +986,6 @@ def point(
1021986
],
1022987
}
1023988

1024-
def validate(self):
1025-
"""Register /validate endpoint."""
1026-
1027-
@self.router.post(
1028-
"/validate",
1029-
operation_id=f"{self.operation_prefix}validate",
1030-
)
1031-
def validate(body: MosaicJSON):
1032-
"""Validate a MosaicJSON"""
1033-
return True
1034-
1035989
def assets(self):
1036990
"""Register /assets endpoint."""
1037991

0 commit comments

Comments
 (0)