eodag 3.10.1__py3-none-any.whl → 4.0.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eodag/__init__.py +6 -1
- eodag/api/collection.py +353 -0
- eodag/api/core.py +606 -641
- eodag/api/product/__init__.py +3 -3
- eodag/api/product/_product.py +74 -56
- eodag/api/product/drivers/__init__.py +4 -46
- eodag/api/product/drivers/base.py +0 -28
- eodag/api/product/metadata_mapping.py +178 -216
- eodag/api/search_result.py +156 -15
- eodag/cli.py +83 -403
- eodag/config.py +81 -51
- eodag/plugins/apis/base.py +2 -2
- eodag/plugins/apis/ecmwf.py +36 -25
- eodag/plugins/apis/usgs.py +55 -40
- eodag/plugins/authentication/base.py +1 -3
- eodag/plugins/crunch/filter_date.py +3 -3
- eodag/plugins/crunch/filter_latest_intersect.py +2 -2
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/download/aws.py +46 -42
- eodag/plugins/download/base.py +13 -14
- eodag/plugins/download/http.py +65 -65
- eodag/plugins/manager.py +28 -29
- eodag/plugins/search/__init__.py +6 -4
- eodag/plugins/search/base.py +131 -80
- eodag/plugins/search/build_search_result.py +245 -173
- eodag/plugins/search/cop_marine.py +87 -56
- eodag/plugins/search/csw.py +47 -37
- eodag/plugins/search/qssearch.py +653 -429
- eodag/plugins/search/stac_list_assets.py +1 -1
- eodag/plugins/search/static_stac_search.py +43 -44
- eodag/resources/{product_types.yml → collections.yml} +2594 -2453
- eodag/resources/ext_collections.json +1 -1
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +2706 -2733
- eodag/resources/stac_provider.yml +50 -92
- eodag/resources/user_conf_template.yml +9 -0
- eodag/types/__init__.py +2 -0
- eodag/types/queryables.py +70 -91
- eodag/types/search_args.py +1 -1
- eodag/utils/__init__.py +97 -21
- eodag/utils/dates.py +0 -12
- eodag/utils/exceptions.py +6 -6
- eodag/utils/free_text_search.py +3 -3
- eodag/utils/repr.py +2 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/METADATA +13 -99
- eodag-4.0.0a2.dist-info/RECORD +93 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/entry_points.txt +0 -4
- eodag/plugins/authentication/oauth.py +0 -60
- eodag/plugins/download/creodias_s3.py +0 -71
- eodag/plugins/download/s3rest.py +0 -351
- eodag/plugins/search/data_request_search.py +0 -565
- eodag/resources/stac.yml +0 -294
- eodag/resources/stac_api.yml +0 -2105
- eodag/rest/__init__.py +0 -24
- eodag/rest/cache.py +0 -70
- eodag/rest/config.py +0 -67
- eodag/rest/constants.py +0 -26
- eodag/rest/core.py +0 -764
- eodag/rest/errors.py +0 -210
- eodag/rest/server.py +0 -604
- eodag/rest/server.wsgi +0 -6
- eodag/rest/stac.py +0 -1032
- eodag/rest/templates/README +0 -1
- eodag/rest/types/__init__.py +0 -18
- eodag/rest/types/collections_search.py +0 -44
- eodag/rest/types/eodag_search.py +0 -386
- eodag/rest/types/queryables.py +0 -174
- eodag/rest/types/stac_search.py +0 -272
- eodag/rest/utils/__init__.py +0 -207
- eodag/rest/utils/cql_evaluate.py +0 -119
- eodag/rest/utils/rfc3339.py +0 -64
- eodag-3.10.1.dist-info/RECORD +0 -116
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/WHEEL +0 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/top_level.txt +0 -0
eodag/rest/server.py
DELETED
|
@@ -1,604 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Copyright 2018, CS GROUP - France, https://www.csgroup.eu/
|
|
3
|
-
#
|
|
4
|
-
# This file is part of EODAG project
|
|
5
|
-
# https://www.github.com/CS-SI/EODAG
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
# you may not use this file except in compliance with the License.
|
|
9
|
-
# You may obtain a copy of the License at
|
|
10
|
-
#
|
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
#
|
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
# See the License for the specific language governing permissions and
|
|
17
|
-
# limitations under the License.
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
import logging
|
|
21
|
-
import os
|
|
22
|
-
import re
|
|
23
|
-
import warnings
|
|
24
|
-
from contextlib import asynccontextmanager
|
|
25
|
-
from importlib.metadata import version
|
|
26
|
-
from json import JSONDecodeError
|
|
27
|
-
from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Optional
|
|
28
|
-
|
|
29
|
-
from fastapi import APIRouter as FastAPIRouter
|
|
30
|
-
from fastapi import FastAPI, HTTPException, Request
|
|
31
|
-
from fastapi.concurrency import run_in_threadpool
|
|
32
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
33
|
-
from fastapi.openapi.utils import get_openapi
|
|
34
|
-
from fastapi.responses import ORJSONResponse
|
|
35
|
-
from pydantic import ValidationError as pydanticValidationError
|
|
36
|
-
from pygeofilter.backends.cql2_json import to_cql2
|
|
37
|
-
from pygeofilter.parsers.cql2_text import parse as parse_cql2_text
|
|
38
|
-
|
|
39
|
-
from eodag.config import load_stac_api_config
|
|
40
|
-
from eodag.rest.cache import init_cache
|
|
41
|
-
from eodag.rest.core import (
|
|
42
|
-
all_collections,
|
|
43
|
-
download_stac_item,
|
|
44
|
-
eodag_api_init,
|
|
45
|
-
get_collection,
|
|
46
|
-
get_detailled_collections_list,
|
|
47
|
-
get_queryables,
|
|
48
|
-
get_stac_api_version,
|
|
49
|
-
get_stac_catalogs,
|
|
50
|
-
get_stac_conformance,
|
|
51
|
-
get_stac_extension_oseo,
|
|
52
|
-
search_stac_items,
|
|
53
|
-
)
|
|
54
|
-
from eodag.rest.errors import add_exception_handlers
|
|
55
|
-
from eodag.rest.types.queryables import QueryablesGetParams
|
|
56
|
-
from eodag.rest.types.stac_search import SearchPostRequest, sortby2list
|
|
57
|
-
from eodag.rest.utils import (
|
|
58
|
-
LIVENESS_PROBE_PATH,
|
|
59
|
-
format_pydantic_error,
|
|
60
|
-
str2json,
|
|
61
|
-
str2list,
|
|
62
|
-
)
|
|
63
|
-
from eodag.utils import parse_header, update_nested_dict
|
|
64
|
-
|
|
65
|
-
if TYPE_CHECKING:
|
|
66
|
-
from fastapi.types import DecoratedCallable
|
|
67
|
-
from requests import Response
|
|
68
|
-
|
|
69
|
-
from starlette.responses import Response as StarletteResponse
|
|
70
|
-
|
|
71
|
-
warnings.warn(
|
|
72
|
-
"The module `eodag.rest.server` is deprecated since v3.9.0 and will be removed in a future version. "
|
|
73
|
-
"The STAC server has moved to https://github.com/CS-SI/stac-fastapi-eodag",
|
|
74
|
-
category=DeprecationWarning,
|
|
75
|
-
stacklevel=2,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
logger = logging.getLogger("eodag.rest.server")
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class APIRouter(FastAPIRouter):
|
|
82
|
-
"""API router"""
|
|
83
|
-
|
|
84
|
-
def api_route(
|
|
85
|
-
self, path: str, *, include_in_schema: bool = True, **kwargs: Any
|
|
86
|
-
) -> Callable[[DecoratedCallable], DecoratedCallable]:
|
|
87
|
-
"""Creates API route decorator"""
|
|
88
|
-
if path == "/":
|
|
89
|
-
return super().api_route(
|
|
90
|
-
path, include_in_schema=include_in_schema, **kwargs
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
if path.endswith("/"):
|
|
94
|
-
path = path[:-1]
|
|
95
|
-
add_path = super().api_route(
|
|
96
|
-
path, include_in_schema=include_in_schema, **kwargs
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
alternate_path = path + "/"
|
|
100
|
-
add_alternate_path = super().api_route(
|
|
101
|
-
alternate_path, include_in_schema=False, **kwargs
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
def decorator(func: DecoratedCallable) -> DecoratedCallable:
|
|
105
|
-
add_alternate_path(func)
|
|
106
|
-
return add_path(func)
|
|
107
|
-
|
|
108
|
-
return decorator
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
router = APIRouter()
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@asynccontextmanager
|
|
115
|
-
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
116
|
-
"""API init and tear-down"""
|
|
117
|
-
eodag_api_init()
|
|
118
|
-
init_cache(app)
|
|
119
|
-
yield
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
app = FastAPI(lifespan=lifespan, title="EODAG", docs_url="/api.html")
|
|
123
|
-
|
|
124
|
-
# conf from resources/stac_api.yml
|
|
125
|
-
stac_api_config = load_stac_api_config()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
@router.api_route(
|
|
129
|
-
methods=["GET", "HEAD"],
|
|
130
|
-
path=LIVENESS_PROBE_PATH,
|
|
131
|
-
include_in_schema=False,
|
|
132
|
-
status_code=200,
|
|
133
|
-
)
|
|
134
|
-
async def liveness_probe(request: Request) -> dict[str, bool]:
|
|
135
|
-
"Endpoint meant to be used as liveness probe by deployment platforms"
|
|
136
|
-
return {"success": True}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
@router.api_route(
|
|
140
|
-
methods=["GET", "HEAD"], path="/api", tags=["Capabilities"], include_in_schema=False
|
|
141
|
-
)
|
|
142
|
-
async def eodag_openapi(request: Request) -> dict[str, Any]:
|
|
143
|
-
"""Customized openapi"""
|
|
144
|
-
logger.debug("URL: /api")
|
|
145
|
-
if app.openapi_schema:
|
|
146
|
-
return app.openapi_schema
|
|
147
|
-
|
|
148
|
-
root_catalog = await get_stac_catalogs(request=request, url="")
|
|
149
|
-
stac_api_version = get_stac_api_version()
|
|
150
|
-
|
|
151
|
-
openapi_schema = get_openapi(
|
|
152
|
-
title=f"{root_catalog['title']} / eodag",
|
|
153
|
-
version=version("eodag"),
|
|
154
|
-
routes=app.routes,
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
# stac_api_config
|
|
158
|
-
update_nested_dict(openapi_schema["paths"], stac_api_config["paths"])
|
|
159
|
-
try:
|
|
160
|
-
update_nested_dict(openapi_schema["components"], stac_api_config["components"])
|
|
161
|
-
except KeyError:
|
|
162
|
-
openapi_schema["components"] = stac_api_config["components"]
|
|
163
|
-
openapi_schema["tags"] = stac_api_config["tags"]
|
|
164
|
-
|
|
165
|
-
detailled_collections_list = get_detailled_collections_list()
|
|
166
|
-
|
|
167
|
-
openapi_schema["info"]["description"] = (
|
|
168
|
-
root_catalog["description"]
|
|
169
|
-
+ f" (stac-api-spec {stac_api_version})"
|
|
170
|
-
+ "<details><summary>Available collections / product types</summary>"
|
|
171
|
-
+ "".join(
|
|
172
|
-
[
|
|
173
|
-
f"[{pt['ID']}](/collections/{pt['ID']} '{pt['title']}') - "
|
|
174
|
-
for pt in detailled_collections_list
|
|
175
|
-
]
|
|
176
|
-
)[:-2]
|
|
177
|
-
+ "</details>"
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
app.openapi_schema = openapi_schema
|
|
181
|
-
return app.openapi_schema
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
app.__setattr__("openapi", eodag_openapi)
|
|
185
|
-
|
|
186
|
-
# Cross-Origin Resource Sharing
|
|
187
|
-
allowed_origins = os.getenv("EODAG_CORS_ALLOWED_ORIGINS")
|
|
188
|
-
allowed_origins_list = allowed_origins.split(",") if allowed_origins else []
|
|
189
|
-
app.add_middleware(
|
|
190
|
-
CORSMiddleware,
|
|
191
|
-
allow_origins=allowed_origins_list,
|
|
192
|
-
allow_credentials=True,
|
|
193
|
-
allow_methods=["*"],
|
|
194
|
-
allow_headers=["*"],
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
add_exception_handlers(app)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
@app.middleware("http")
|
|
201
|
-
async def forward_middleware(
|
|
202
|
-
request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
203
|
-
) -> Response:
|
|
204
|
-
"""Middleware that handles forward headers and sets request.state.url*"""
|
|
205
|
-
|
|
206
|
-
forwarded_host = request.headers.get("x-forwarded-host")
|
|
207
|
-
forwarded_proto = request.headers.get("x-forwarded-proto")
|
|
208
|
-
|
|
209
|
-
if "forwarded" in request.headers:
|
|
210
|
-
header_forwarded = parse_header(request.headers["forwarded"])
|
|
211
|
-
forwarded_host = str(header_forwarded.get_param("host", None)) or forwarded_host
|
|
212
|
-
forwarded_proto = (
|
|
213
|
-
str(header_forwarded.get_param("proto", None)) or forwarded_proto
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
request.state.url_root = f"{forwarded_proto or request.url.scheme}://{forwarded_host or request.url.netloc}"
|
|
217
|
-
request.state.url = f"{request.state.url_root}{request.url.path}"
|
|
218
|
-
|
|
219
|
-
response = await call_next(request)
|
|
220
|
-
return response
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
@router.api_route(methods=["GET", "HEAD"], path="/", tags=["Capabilities"])
|
|
224
|
-
async def catalogs_root(request: Request) -> ORJSONResponse:
|
|
225
|
-
"""STAC catalogs root"""
|
|
226
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
227
|
-
|
|
228
|
-
response = await get_stac_catalogs(
|
|
229
|
-
request=request,
|
|
230
|
-
url=request.state.url,
|
|
231
|
-
provider=request.query_params.get("provider"),
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
return ORJSONResponse(response)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
@router.api_route(methods=["GET", "HEAD"], path="/conformance", tags=["Capabilities"])
|
|
238
|
-
def conformance(request: Request) -> ORJSONResponse:
|
|
239
|
-
"""STAC conformance"""
|
|
240
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
241
|
-
response = get_stac_conformance()
|
|
242
|
-
|
|
243
|
-
return ORJSONResponse(response)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
@router.api_route(
|
|
247
|
-
methods=["GET", "HEAD"],
|
|
248
|
-
path="/extensions/oseo/json-schema/schema.json",
|
|
249
|
-
include_in_schema=False,
|
|
250
|
-
)
|
|
251
|
-
def stac_extension_oseo(request: Request) -> ORJSONResponse:
|
|
252
|
-
"""STAC OGC / OpenSearch extension for EO"""
|
|
253
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
254
|
-
response = get_stac_extension_oseo(url=request.state.url)
|
|
255
|
-
|
|
256
|
-
return ORJSONResponse(response)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
@router.api_route(
|
|
260
|
-
methods=["GET", "HEAD"],
|
|
261
|
-
path="/collections/{collection_id}/items/{item_id}/download",
|
|
262
|
-
tags=["Data"],
|
|
263
|
-
include_in_schema=False,
|
|
264
|
-
)
|
|
265
|
-
def stac_collections_item_download(
|
|
266
|
-
collection_id: str, item_id: str, request: Request
|
|
267
|
-
) -> StarletteResponse:
|
|
268
|
-
"""STAC collection item download"""
|
|
269
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
270
|
-
|
|
271
|
-
arguments = dict(request.query_params)
|
|
272
|
-
provider = arguments.pop("provider", None)
|
|
273
|
-
|
|
274
|
-
return download_stac_item(
|
|
275
|
-
request=request,
|
|
276
|
-
collection_id=collection_id,
|
|
277
|
-
item_id=item_id,
|
|
278
|
-
provider=provider,
|
|
279
|
-
**arguments,
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
@router.api_route(
|
|
284
|
-
methods=["GET", "HEAD"],
|
|
285
|
-
path="/collections/{collection_id}/items/{item_id}/download/{asset}",
|
|
286
|
-
tags=["Data"],
|
|
287
|
-
include_in_schema=False,
|
|
288
|
-
)
|
|
289
|
-
def stac_collections_item_download_asset(
|
|
290
|
-
collection_id: str, item_id: str, asset: str, request: Request
|
|
291
|
-
):
|
|
292
|
-
"""STAC collection item asset download"""
|
|
293
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
294
|
-
|
|
295
|
-
arguments = dict(request.query_params)
|
|
296
|
-
provider = arguments.pop("provider", None)
|
|
297
|
-
|
|
298
|
-
return download_stac_item(
|
|
299
|
-
request=request,
|
|
300
|
-
collection_id=collection_id,
|
|
301
|
-
item_id=item_id,
|
|
302
|
-
provider=provider,
|
|
303
|
-
asset=asset,
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
@router.api_route(
|
|
308
|
-
methods=["GET", "HEAD"],
|
|
309
|
-
path="/collections/{collection_id}/items/{item_id}",
|
|
310
|
-
tags=["Data"],
|
|
311
|
-
include_in_schema=False,
|
|
312
|
-
)
|
|
313
|
-
def stac_collections_item(
|
|
314
|
-
collection_id: str, item_id: str, request: Request, provider: Optional[str] = None
|
|
315
|
-
) -> ORJSONResponse:
|
|
316
|
-
"""STAC collection item by id"""
|
|
317
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
318
|
-
|
|
319
|
-
search_request = SearchPostRequest(
|
|
320
|
-
provider=provider, ids=[item_id], collections=[collection_id], limit=1
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
item_collection = search_stac_items(request, search_request)
|
|
324
|
-
|
|
325
|
-
if not item_collection["features"]:
|
|
326
|
-
raise HTTPException(
|
|
327
|
-
status_code=404,
|
|
328
|
-
detail=f"Item {item_id} in Collection {collection_id} does not exist.",
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
return ORJSONResponse(item_collection["features"][0])
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
@router.api_route(
|
|
335
|
-
methods=["GET", "HEAD"],
|
|
336
|
-
path="/collections/{collection_id}/items",
|
|
337
|
-
tags=["Data"],
|
|
338
|
-
include_in_schema=False,
|
|
339
|
-
)
|
|
340
|
-
def stac_collections_items(
|
|
341
|
-
collection_id: str,
|
|
342
|
-
request: Request,
|
|
343
|
-
provider: Optional[str] = None,
|
|
344
|
-
bbox: Optional[str] = None,
|
|
345
|
-
datetime: Optional[str] = None,
|
|
346
|
-
limit: Optional[int] = None,
|
|
347
|
-
query: Optional[str] = None,
|
|
348
|
-
page: Optional[int] = None,
|
|
349
|
-
sortby: Optional[str] = None,
|
|
350
|
-
filter: Optional[str] = None,
|
|
351
|
-
filter_lang: Optional[str] = "cql2-text",
|
|
352
|
-
crunch: Optional[str] = None,
|
|
353
|
-
) -> ORJSONResponse:
|
|
354
|
-
"""Fetch collection's features"""
|
|
355
|
-
|
|
356
|
-
return get_search(
|
|
357
|
-
request=request,
|
|
358
|
-
provider=provider,
|
|
359
|
-
collections=collection_id,
|
|
360
|
-
bbox=bbox,
|
|
361
|
-
datetime=datetime,
|
|
362
|
-
limit=limit,
|
|
363
|
-
query=query,
|
|
364
|
-
page=page,
|
|
365
|
-
sortby=sortby,
|
|
366
|
-
filter=filter,
|
|
367
|
-
filter_lang=filter_lang,
|
|
368
|
-
crunch=crunch,
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
@router.api_route(
|
|
373
|
-
methods=["GET", "HEAD"],
|
|
374
|
-
path="/collections/{collection_id}/queryables",
|
|
375
|
-
tags=["Capabilities"],
|
|
376
|
-
include_in_schema=False,
|
|
377
|
-
response_model_exclude_none=True,
|
|
378
|
-
)
|
|
379
|
-
async def list_collection_queryables(
|
|
380
|
-
request: Request,
|
|
381
|
-
collection_id: str,
|
|
382
|
-
) -> ORJSONResponse:
|
|
383
|
-
"""Returns the list of queryable properties for a specific collection.
|
|
384
|
-
|
|
385
|
-
This endpoint provides a list of properties that can be used as filters when querying
|
|
386
|
-
the specified collection. These properties correspond to characteristics of the data
|
|
387
|
-
that can be filtered using comparison operators.
|
|
388
|
-
|
|
389
|
-
:param request: The incoming request object.
|
|
390
|
-
:param collection_id: The identifier of the collection for which to retrieve queryable properties.
|
|
391
|
-
:returns: A json object containing the list of available queryable properties for the specified collection.
|
|
392
|
-
"""
|
|
393
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
394
|
-
|
|
395
|
-
params: dict[str, list[Any]] = {}
|
|
396
|
-
for k, v in request.query_params.multi_items():
|
|
397
|
-
params.setdefault(k, []).append(v)
|
|
398
|
-
|
|
399
|
-
provider = params.pop("provider", [None])[0]
|
|
400
|
-
datetime = params.pop("datetime", [None])[0]
|
|
401
|
-
|
|
402
|
-
queryables = await get_queryables(
|
|
403
|
-
request,
|
|
404
|
-
QueryablesGetParams.model_validate(
|
|
405
|
-
{
|
|
406
|
-
**params,
|
|
407
|
-
**{
|
|
408
|
-
"collection": collection_id,
|
|
409
|
-
"datetime": datetime,
|
|
410
|
-
},
|
|
411
|
-
}
|
|
412
|
-
),
|
|
413
|
-
provider=provider,
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
return ORJSONResponse(queryables)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
@router.api_route(
|
|
420
|
-
methods=["GET", "HEAD"],
|
|
421
|
-
path="/collections/{collection_id}",
|
|
422
|
-
tags=["Capabilities"],
|
|
423
|
-
include_in_schema=False,
|
|
424
|
-
)
|
|
425
|
-
async def collection_by_id(
|
|
426
|
-
collection_id: str, request: Request, provider: Optional[str] = None
|
|
427
|
-
) -> ORJSONResponse:
|
|
428
|
-
"""STAC collection by id"""
|
|
429
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
430
|
-
|
|
431
|
-
response = await get_collection(
|
|
432
|
-
request=request,
|
|
433
|
-
collection_id=collection_id,
|
|
434
|
-
provider=provider,
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
return ORJSONResponse(response)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
@router.api_route(
|
|
441
|
-
methods=["GET", "HEAD"],
|
|
442
|
-
path="/collections",
|
|
443
|
-
tags=["Capabilities"],
|
|
444
|
-
include_in_schema=False,
|
|
445
|
-
)
|
|
446
|
-
async def collections(
|
|
447
|
-
request: Request,
|
|
448
|
-
provider: Optional[str] = None,
|
|
449
|
-
q: Optional[str] = None,
|
|
450
|
-
bbox: Optional[str] = None,
|
|
451
|
-
platform: Optional[str] = None,
|
|
452
|
-
instrument: Optional[str] = None,
|
|
453
|
-
constellation: Optional[str] = None,
|
|
454
|
-
datetime: Optional[str] = None,
|
|
455
|
-
) -> ORJSONResponse:
|
|
456
|
-
"""STAC collections
|
|
457
|
-
|
|
458
|
-
Can be filtered using parameters: instrument, platform, platformSerialIdentifier, sensorType,
|
|
459
|
-
processingLevel
|
|
460
|
-
"""
|
|
461
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
462
|
-
|
|
463
|
-
collections = await all_collections(
|
|
464
|
-
request, provider, q, platform, instrument, constellation, datetime, bbox
|
|
465
|
-
)
|
|
466
|
-
|
|
467
|
-
return ORJSONResponse(collections)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
@router.api_route(
|
|
471
|
-
methods=["GET", "HEAD"],
|
|
472
|
-
path="/queryables",
|
|
473
|
-
tags=["Capabilities"],
|
|
474
|
-
response_model_exclude_none=True,
|
|
475
|
-
include_in_schema=False,
|
|
476
|
-
)
|
|
477
|
-
async def list_queryables(request: Request) -> ORJSONResponse:
|
|
478
|
-
"""Returns the list of terms available for use when writing filter expressions.
|
|
479
|
-
|
|
480
|
-
This endpoint provides a list of terms that can be used as filters when querying
|
|
481
|
-
the data. These terms correspond to properties that can be filtered using comparison
|
|
482
|
-
operators.
|
|
483
|
-
|
|
484
|
-
:param request: The incoming request object.
|
|
485
|
-
:returns: A json object containing the list of available queryable terms.
|
|
486
|
-
"""
|
|
487
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
488
|
-
additional_params = dict(request.query_params.items())
|
|
489
|
-
provider = additional_params.pop("provider", None)
|
|
490
|
-
queryables = await get_queryables(
|
|
491
|
-
request, QueryablesGetParams(**additional_params), provider=provider
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
return ORJSONResponse(queryables)
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
@router.api_route(
|
|
498
|
-
methods=["GET", "HEAD"],
|
|
499
|
-
path="/search",
|
|
500
|
-
tags=["STAC"],
|
|
501
|
-
include_in_schema=False,
|
|
502
|
-
)
|
|
503
|
-
def get_search(
|
|
504
|
-
request: Request,
|
|
505
|
-
provider: Optional[str] = None,
|
|
506
|
-
collections: Optional[str] = None,
|
|
507
|
-
ids: Optional[str] = None,
|
|
508
|
-
bbox: Optional[str] = None,
|
|
509
|
-
datetime: Optional[str] = None,
|
|
510
|
-
intersects: Optional[str] = None,
|
|
511
|
-
limit: Optional[int] = None,
|
|
512
|
-
query: Optional[str] = None,
|
|
513
|
-
page: Optional[int] = None,
|
|
514
|
-
sortby: Optional[str] = None,
|
|
515
|
-
filter: Optional[str] = None, # pylint: disable=redefined-builtin
|
|
516
|
-
filter_lang: Optional[str] = "cql2-text",
|
|
517
|
-
crunch: Optional[str] = None,
|
|
518
|
-
) -> ORJSONResponse:
|
|
519
|
-
"""Handler for GET /search"""
|
|
520
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
521
|
-
|
|
522
|
-
query_params = str(request.query_params)
|
|
523
|
-
|
|
524
|
-
# Kludgy fix because using factory does not allow alias for filter-lang
|
|
525
|
-
if filter_lang is None:
|
|
526
|
-
match = re.search(r"filter-lang=([a-z0-9-]+)", query_params, re.IGNORECASE)
|
|
527
|
-
if match:
|
|
528
|
-
filter_lang = match.group(1)
|
|
529
|
-
|
|
530
|
-
base_args = {
|
|
531
|
-
"provider": provider,
|
|
532
|
-
"collections": str2list(collections),
|
|
533
|
-
"ids": str2list(ids),
|
|
534
|
-
"datetime": datetime,
|
|
535
|
-
"bbox": str2list(bbox),
|
|
536
|
-
"intersects": str2json("intersects", intersects),
|
|
537
|
-
"limit": limit,
|
|
538
|
-
"query": str2json("query", query),
|
|
539
|
-
"page": page,
|
|
540
|
-
"sortby": sortby2list(sortby),
|
|
541
|
-
"crunch": crunch,
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if filter:
|
|
545
|
-
if filter_lang == "cql2-text":
|
|
546
|
-
ast = parse_cql2_text(filter)
|
|
547
|
-
base_args["filter"] = str2json("filter", to_cql2(ast)) # type: ignore
|
|
548
|
-
base_args["filter-lang"] = "cql2-json"
|
|
549
|
-
elif filter_lang == "cql-json":
|
|
550
|
-
base_args["filter"] = str2json(filter)
|
|
551
|
-
|
|
552
|
-
clean = {k: v for k, v in base_args.items() if v is not None and v != []}
|
|
553
|
-
|
|
554
|
-
try:
|
|
555
|
-
search_request = SearchPostRequest.model_validate(clean)
|
|
556
|
-
except pydanticValidationError as e:
|
|
557
|
-
raise HTTPException(status_code=400, detail=format_pydantic_error(e)) from e
|
|
558
|
-
|
|
559
|
-
response = search_stac_items(
|
|
560
|
-
request=request,
|
|
561
|
-
search_request=search_request,
|
|
562
|
-
)
|
|
563
|
-
return ORJSONResponse(content=response, media_type="application/json")
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
@router.api_route(
|
|
567
|
-
methods=["POST", "HEAD"],
|
|
568
|
-
path="/search",
|
|
569
|
-
tags=["STAC"],
|
|
570
|
-
include_in_schema=False,
|
|
571
|
-
)
|
|
572
|
-
async def post_search(request: Request) -> ORJSONResponse:
|
|
573
|
-
"""STAC post search"""
|
|
574
|
-
logger.info(f"{request.method} {request.state.url}")
|
|
575
|
-
|
|
576
|
-
content_type = request.headers.get("Content-Type")
|
|
577
|
-
|
|
578
|
-
if content_type is None:
|
|
579
|
-
raise HTTPException(status_code=400, detail="No Content-Type provided")
|
|
580
|
-
if content_type != "application/json":
|
|
581
|
-
raise HTTPException(status_code=400, detail="Content-Type not supported")
|
|
582
|
-
|
|
583
|
-
try:
|
|
584
|
-
payload = await request.json()
|
|
585
|
-
except JSONDecodeError as e:
|
|
586
|
-
raise HTTPException(status_code=400, detail="Invalid JSON data") from e
|
|
587
|
-
|
|
588
|
-
try:
|
|
589
|
-
search_request = SearchPostRequest.model_validate(payload)
|
|
590
|
-
except pydanticValidationError as e:
|
|
591
|
-
raise HTTPException(status_code=400, detail=format_pydantic_error(e)) from e
|
|
592
|
-
|
|
593
|
-
logger.debug("Body: %s", search_request.model_dump(exclude_none=True))
|
|
594
|
-
|
|
595
|
-
response = await run_in_threadpool(
|
|
596
|
-
search_stac_items,
|
|
597
|
-
request,
|
|
598
|
-
search_request,
|
|
599
|
-
)
|
|
600
|
-
|
|
601
|
-
return ORJSONResponse(content=response, media_type="application/json")
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
app.include_router(router)
|