oapi-profile-builder 3.6.2__tar.gz → 3.7.0__tar.gz
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.
- {oapi_profile_builder-3.6.2/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.7.0}/PKG-INFO +1 -1
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/pyproject.toml +1 -1
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/generate.py +303 -347
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0/src/oapi_profile_builder.egg-info}/PKG-INFO +1 -1
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/LICENSE +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/README.md +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/setup.cfg +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/__init__.py +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cite.py +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cite_features.py +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cli.py +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/compile.py +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cover.py +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/models.py +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/server_validation.py +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
- {oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "oapi-profile-builder"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.7.0"
|
|
8
8
|
description = "Authoritative tooling for creating OGC API Service Profiles (EDR, Features)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
{oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/generate.py
RENAMED
|
@@ -37,7 +37,7 @@ from __future__ import annotations
|
|
|
37
37
|
|
|
38
38
|
from pathlib import Path
|
|
39
39
|
|
|
40
|
-
import yaml
|
|
40
|
+
import yaml, copy
|
|
41
41
|
|
|
42
42
|
from edr_pydantic.collections import Collection
|
|
43
43
|
from oapi_profile_builder.models import ServiceProfile
|
|
@@ -79,6 +79,7 @@ _LANG = {"$ref": "#/components/parameters/lang"}
|
|
|
79
79
|
_DATETIME = {"$ref": f"{_FEATURES}#/components/parameters/datetime"}
|
|
80
80
|
_PARAM_NAME = {"$ref": f"{_EDR}/parameters/parameter-name.yaml"}
|
|
81
81
|
_Z = {"$ref": f"{_EDR}/parameters/z.yaml"}
|
|
82
|
+
_CRS = {"$ref": f"{_EDR}/parameters/crs.yaml"}
|
|
82
83
|
|
|
83
84
|
# Error responses follow RFC 9457 (Problem Details for HTTP APIs), served as
|
|
84
85
|
# application/problem+json, per OGC API - EDR Part 3 error-handling requirement
|
|
@@ -135,45 +136,71 @@ _LINK_SCHEMA = {
|
|
|
135
136
|
_LINKS_ARRAY = {"type": "array", "items": _LINK_SCHEMA}
|
|
136
137
|
|
|
137
138
|
# Landing page response with required profile link per REQ_publishing
|
|
138
|
-
def _landing_page_schema(
|
|
139
|
+
def _landing_page_schema(profile: "ServiceProfile") -> dict:
|
|
140
|
+
"""Build a 200 response schema for the landing page with full metadata."""
|
|
141
|
+
version = profile.openapi_version
|
|
142
|
+
profile_uri = profile.req_uri
|
|
143
|
+
|
|
144
|
+
# Recommended link types for OGC API - EDR landing page
|
|
145
|
+
link_rels = ["self", "service-desc", "service-doc", "conformance", "data", "profile"]
|
|
146
|
+
link_items = {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"required": ["href", "rel"],
|
|
149
|
+
"properties": {
|
|
150
|
+
"href": {"type": "string"},
|
|
151
|
+
"rel": {"type": "string", "enum": link_rels},
|
|
152
|
+
"type": {"type": "string"},
|
|
153
|
+
"title": {"type": "string"}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
139
157
|
schema_props: dict = {
|
|
140
|
-
"links": {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"type": "object",
|
|
145
|
-
"required": ["href", "rel"],
|
|
146
|
-
"properties": {
|
|
147
|
-
"rel": _const("profile", version),
|
|
148
|
-
"href": _const(profile_uri, version),
|
|
149
|
-
},
|
|
150
|
-
}, version) if version == "3.1.0" else {}),
|
|
151
|
-
},
|
|
158
|
+
"links": {"type": "array", "items": link_items},
|
|
159
|
+
"title": {"type": "string", **_const(profile.title, version)},
|
|
160
|
+
"version": {"type": "string", **_const(profile.version, version)},
|
|
161
|
+
"x-ogc-profile": {"type": "string", **_const(profile_uri, version)}
|
|
152
162
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
|
|
164
|
+
if profile.description:
|
|
165
|
+
schema_props["description"] = {"type": "string", **_const(profile.description, version)}
|
|
166
|
+
if profile.keywords:
|
|
167
|
+
schema_props["keywords"] = {"type": "array", "items": {"type": "string", "enum": profile.keywords}}
|
|
168
|
+
|
|
169
|
+
if profile.provider:
|
|
170
|
+
p = profile.provider
|
|
171
|
+
provider_props = {"name": {"type": "string", **_const(p.name, version)}}
|
|
172
|
+
if p.url: provider_props["url"] = {"type": "string", **_const(p.url, version)}
|
|
173
|
+
if p.contact:
|
|
174
|
+
c = p.contact
|
|
175
|
+
contact_props = {}
|
|
176
|
+
if c.email: contact_props["email"] = {"type": "string", **_const(c.email, version)}
|
|
177
|
+
for key in ("phone", "hours", "instructions", "address", "postalcode", "city", "country"):
|
|
178
|
+
val = getattr(c, key, None)
|
|
179
|
+
if val: contact_props[f"x-{key}"] = {"type": "string", **_const(val, version)}
|
|
180
|
+
if contact_props: provider_props["contact"] = {"type": "object", "properties": contact_props}
|
|
181
|
+
schema_props["provider"] = {"type": "object", "properties": provider_props}
|
|
182
|
+
|
|
183
|
+
if profile.classification:
|
|
184
|
+
cl = profile.classification
|
|
185
|
+
schema_props["x-classification"] = {
|
|
186
|
+
"type": "object",
|
|
187
|
+
"properties": {
|
|
188
|
+
"level": {"type": "string", **_const(cl.level, version)},
|
|
189
|
+
"system": {"type": "string", **_const(cl.system, version)} if cl.system else {"type": "string"}
|
|
190
|
+
}
|
|
162
191
|
}
|
|
163
192
|
|
|
193
|
+
if profile.metadata_date:
|
|
194
|
+
schema_props["x-metadata-date"] = {"type": "string", **_const(profile.metadata_date, version)}
|
|
195
|
+
if profile.resource_service_publish_date:
|
|
196
|
+
schema_props["x-resource-publish-date"] = {"type": "string", **_const(profile.resource_service_publish_date, version)}
|
|
197
|
+
|
|
164
198
|
return {
|
|
165
199
|
"description": "Landing page",
|
|
166
|
-
"content": {
|
|
167
|
-
"application/json": {
|
|
168
|
-
"schema": {
|
|
169
|
-
"type": "object",
|
|
170
|
-
"required": ["links"],
|
|
171
|
-
"properties": schema_props,
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
},
|
|
200
|
+
"content": {"application/json": {"schema": {"type": "object", "required": ["links"], "properties": schema_props}}}
|
|
175
201
|
}
|
|
176
202
|
|
|
203
|
+
|
|
177
204
|
_R200_CONFORMANCE = {
|
|
178
205
|
"description": "Conformance classes",
|
|
179
206
|
"content": {
|
|
@@ -223,6 +250,7 @@ def _locations_response_schema(profile: "ServiceProfile | None") -> dict:
|
|
|
223
250
|
properties named in ``profile.locations_feature_required_properties`` are
|
|
224
251
|
marked required so the generated OpenAPI enforces them.
|
|
225
252
|
"""
|
|
253
|
+
version = profile.openapi_version if profile else "3.0.0"
|
|
226
254
|
required_props = list(getattr(profile, "locations_feature_required_properties", None) or [])
|
|
227
255
|
properties_schema: dict = {
|
|
228
256
|
"type": "object",
|
|
@@ -271,6 +299,8 @@ def _coverage_response(coll: Collection, profile: "ServiceProfile | None") -> di
|
|
|
271
299
|
coll_formats: list[str] = []
|
|
272
300
|
if coll.output_formats:
|
|
273
301
|
coll_formats = list(coll.output_formats)
|
|
302
|
+
elif profile and profile.output_formats:
|
|
303
|
+
coll_formats = [f.name for f in profile.output_formats]
|
|
274
304
|
|
|
275
305
|
content: dict = {}
|
|
276
306
|
if coll_formats:
|
|
@@ -278,15 +308,8 @@ def _coverage_response(coll: Collection, profile: "ServiceProfile | None") -> di
|
|
|
278
308
|
schema_ref = fmt_map.get(fmt_name)
|
|
279
309
|
if fmt_name == "CoverageJSON":
|
|
280
310
|
media_type = "application/prs.coverage+json"
|
|
281
|
-
|
|
282
|
-
alt_media = "application/vnd.cov+json"
|
|
283
|
-
if schema_ref:
|
|
284
|
-
schema = {"$ref": schema_ref}
|
|
285
|
-
else:
|
|
286
|
-
# Fallback to an inline object if external ref is problematic
|
|
287
|
-
schema = {"type": "object"}
|
|
311
|
+
schema = {"$ref": schema_ref} if schema_ref else {"type": "object"}
|
|
288
312
|
content[media_type] = {"schema": schema}
|
|
289
|
-
content[alt_media] = {"schema": schema}
|
|
290
313
|
elif fmt_name == "GeoJSON":
|
|
291
314
|
media_type = "application/geo+json"
|
|
292
315
|
schema = {"$ref": schema_ref} if schema_ref else {"type": "object"}
|
|
@@ -301,11 +324,11 @@ def _coverage_response(coll: Collection, profile: "ServiceProfile | None") -> di
|
|
|
301
324
|
schema = {"$ref": pf.schema_ref} if pf.schema_ref else {"type": "object"}
|
|
302
325
|
content[pf.media_type] = {"schema": schema}
|
|
303
326
|
break
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
327
|
+
|
|
328
|
+
if not content:
|
|
329
|
+
# Fallback: GeoJSON and CoverageJSON
|
|
330
|
+
content["application/geo+json"] = {"schema": {"type": "object"}}
|
|
331
|
+
content["application/prs.coverage+json"] = {"schema": {"type": "object"}}
|
|
309
332
|
|
|
310
333
|
# Per the EDR error-handling requirement, a query whose spatio-temporal
|
|
311
334
|
# selection falls inside the collection extent but matches no data returns
|
|
@@ -330,8 +353,9 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
330
353
|
"required": ["coords"],
|
|
331
354
|
"properties": {
|
|
332
355
|
"coords": {"type": "string", "description": "WKT Point geometry (e.g. POINT(0 51.5))"},
|
|
333
|
-
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval
|
|
356
|
+
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
334
357
|
"parameter-name": {"type": "string", "description": "Comma-separated list of parameter names to return"},
|
|
358
|
+
"crs": {"type": "string", "description": "Identifier for the coordinate reference system"},
|
|
335
359
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
336
360
|
"f": {"type": "string", "description": "Response format"},
|
|
337
361
|
},
|
|
@@ -343,6 +367,7 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
343
367
|
"coords": {"type": "string", "description": "WKT Polygon geometry"},
|
|
344
368
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
345
369
|
"parameter-name": {"type": "string", "description": "Comma-separated list of parameter names to return"},
|
|
370
|
+
"crs": {"type": "string", "description": "Identifier for the coordinate reference system"},
|
|
346
371
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
347
372
|
"f": {"type": "string", "description": "Response format"},
|
|
348
373
|
},
|
|
@@ -356,6 +381,7 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
356
381
|
"within-units": {"type": "string", "description": "Units for the within parameter (e.g. km)"},
|
|
357
382
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
358
383
|
"parameter-name": {"type": "string", "description": "Comma-separated list of parameter names to return"},
|
|
384
|
+
"crs": {"type": "string", "description": "Identifier for the coordinate reference system"},
|
|
359
385
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
360
386
|
"f": {"type": "string", "description": "Response format"},
|
|
361
387
|
},
|
|
@@ -373,6 +399,7 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
373
399
|
},
|
|
374
400
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
375
401
|
"parameter-name": {"type": "string", "description": "Comma-separated list of parameter names to return"},
|
|
402
|
+
"crs": {"type": "string", "description": "Identifier for the coordinate reference system"},
|
|
376
403
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
377
404
|
"f": {"type": "string", "description": "Response format"},
|
|
378
405
|
},
|
|
@@ -384,6 +411,7 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
384
411
|
"coords": {"type": "string", "description": "WKT LineString geometry"},
|
|
385
412
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
386
413
|
"parameter-name": {"type": "string", "description": "Comma-separated list of parameter names to return"},
|
|
414
|
+
"crs": {"type": "string", "description": "Identifier for the coordinate reference system"},
|
|
387
415
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
388
416
|
"f": {"type": "string", "description": "Response format"},
|
|
389
417
|
},
|
|
@@ -396,41 +424,22 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
396
424
|
"corridor-width": {"type": "number", "description": "Width of the corridor"},
|
|
397
425
|
"corridor-height": {"type": "number", "description": "Height of the corridor"},
|
|
398
426
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
399
|
-
"
|
|
400
|
-
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
427
|
+
"crs": {"type": "string", "description": "Identifier for the coordinate reference system"},
|
|
401
428
|
"f": {"type": "string", "description": "Response format"},
|
|
402
429
|
},
|
|
403
|
-
},
|
|
404
|
-
"items": {
|
|
405
|
-
"type": "object",
|
|
406
|
-
"properties": {
|
|
407
|
-
"bbox": {"type": "string", "description": "Bounding box filter"},
|
|
408
|
-
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
409
|
-
"parameter-name": {"type": "string", "description": "Comma-separated list of parameter names to return"},
|
|
410
|
-
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
411
|
-
"f": {"type": "string", "description": "Response format"},
|
|
412
|
-
},
|
|
413
|
-
},
|
|
414
|
-
"locations": {
|
|
415
|
-
"type": "object",
|
|
416
|
-
"properties": {
|
|
417
|
-
"bbox": {
|
|
418
|
-
"type": "array",
|
|
419
|
-
"description": "Bounding box [minLon, minLat, maxLon, maxLat]",
|
|
420
|
-
"items": {"type": "number"},
|
|
421
|
-
"minItems": 4,
|
|
422
|
-
"maxItems": 6,
|
|
423
|
-
},
|
|
424
|
-
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
425
|
-
"f": {"type": "string", "description": "Response format"},
|
|
426
|
-
},
|
|
427
|
-
},
|
|
428
430
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict) -> dict:
|
|
431
|
+
},
|
|
432
|
+
def _post_operation(get_op: dict, query_type: str, coll: object, responses: dict) -> dict:
|
|
432
433
|
"""Build a POST operation mirroring a GET EDR data query operation."""
|
|
433
434
|
body_schema = _POST_BODY_SCHEMAS.get(query_type, {"type": "object"})
|
|
435
|
+
import copy
|
|
436
|
+
body_schema = copy.deepcopy(body_schema)
|
|
437
|
+
|
|
438
|
+
# Filter out 'z' if collection has no vertical extent
|
|
439
|
+
if hasattr(coll, 'extent') and coll.extent and not coll.extent.vertical:
|
|
440
|
+
if "properties" in body_schema and "z" in body_schema["properties"]:
|
|
441
|
+
del body_schema["properties"]["z"]
|
|
442
|
+
|
|
434
443
|
# Carry over a default output format from the GET operation's `f` parameter
|
|
435
444
|
# so the POST body documents the same default.
|
|
436
445
|
default_f = None
|
|
@@ -439,8 +448,6 @@ def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict
|
|
|
439
448
|
default_f = p.get("schema", {}).get("default")
|
|
440
449
|
break
|
|
441
450
|
if default_f and "properties" in body_schema and "f" in body_schema["properties"]:
|
|
442
|
-
import copy
|
|
443
|
-
body_schema = copy.deepcopy(body_schema)
|
|
444
451
|
body_schema["properties"]["f"]["default"] = default_f
|
|
445
452
|
return {
|
|
446
453
|
"summary": get_op["summary"].replace("query", "query (POST)"),
|
|
@@ -464,35 +471,35 @@ def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict
|
|
|
464
471
|
_QUERY_PARAMS: dict[str, list[dict]] = {
|
|
465
472
|
"position": [
|
|
466
473
|
{"$ref": f"{_EDR}/parameters/positionCoords.yaml"},
|
|
467
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
474
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
468
475
|
],
|
|
469
476
|
"area": [
|
|
470
477
|
{"$ref": f"{_EDR}/parameters/areaCoords.yaml"},
|
|
471
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
478
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
472
479
|
],
|
|
473
480
|
"radius": [
|
|
474
481
|
{"$ref": f"{_EDR}/parameters/positionCoords.yaml"},
|
|
475
482
|
{"$ref": f"{_EDR}/parameters/within.yaml"},
|
|
476
483
|
{"name": "within-units", "in": "query", "required": True, "description": "Units for the within parameter e.g. km", "schema": {"type": "string"}},
|
|
477
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
484
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
478
485
|
],
|
|
479
486
|
"cube": [
|
|
480
487
|
{"$ref": f"{_EDR}/parameters/bbox.yaml"},
|
|
481
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
488
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
482
489
|
],
|
|
483
490
|
"trajectory": [
|
|
484
491
|
{"name": "coords", "in": "query", "required": True, "description": "WKT LineString geometry", "schema": {"type": "string"}},
|
|
485
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
492
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
486
493
|
],
|
|
487
494
|
"corridor": [
|
|
488
495
|
{"name": "coords", "in": "query", "required": True, "description": "WKT LineString geometry", "schema": {"type": "string"}},
|
|
489
496
|
{"name": "corridor-width", "in": "query", "required": True, "description": "Width of the corridor", "schema": {"type": "number"}},
|
|
490
497
|
{"name": "corridor-height", "in": "query", "description": "Height of the corridor", "schema": {"type": "number"}},
|
|
491
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
498
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
492
499
|
],
|
|
493
500
|
"items": [
|
|
494
501
|
{"name": "bbox", "in": "query", "description": "Bounding box filter", "schema": {"type": "string"}},
|
|
495
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
502
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
496
503
|
],
|
|
497
504
|
"locations": [
|
|
498
505
|
{"$ref": f"{_EDR}/parameters/bbox.yaml"},
|
|
@@ -501,257 +508,14 @@ _QUERY_PARAMS: dict[str, list[dict]] = {
|
|
|
501
508
|
"instances": [_F],
|
|
502
509
|
}
|
|
503
510
|
|
|
504
|
-
|
|
505
511
|
def _operation_id(*parts: str) -> str:
|
|
506
|
-
"""Build a valid operationId (letters, digits, underscores only).
|
|
507
|
-
|
|
508
|
-
Each part is sanitized by splitting on non-alphanumeric characters,
|
|
509
|
-
capitalizing the first letter of each word while preserving the rest,
|
|
510
|
-
then joining everything together.
|
|
511
|
-
"""
|
|
512
512
|
import re as _re
|
|
513
|
-
|
|
514
513
|
def sanitize(s: str) -> str:
|
|
515
514
|
words = _re.split(r"[^a-zA-Z0-9]+", s)
|
|
516
515
|
return "".join((w[0].upper() + w[1:]) for w in words if w)
|
|
517
|
-
|
|
518
516
|
return "".join(sanitize(p) for p in parts)
|
|
519
517
|
|
|
520
518
|
|
|
521
|
-
def _collection_response_schema(coll: Collection,
|
|
522
|
-
profile: "ServiceProfile | None") -> dict:
|
|
523
|
-
"""Build a 200 response schema for a single collection endpoint.
|
|
524
|
-
|
|
525
|
-
Includes:
|
|
526
|
-
- CRS constraints from extent_requirements (enum or pattern)
|
|
527
|
-
- TRS/VRS constraints from extent_requirements
|
|
528
|
-
- Full temporal extent schema (interval, values, trs)
|
|
529
|
-
- Collection-level crs array
|
|
530
|
-
- Full parameter_names objects with all fields
|
|
531
|
-
- parameter_name_pattern as propertyNames constraint
|
|
532
|
-
"""
|
|
533
|
-
version = profile.openapi_version if profile else "3.0.0"
|
|
534
|
-
crs_schema: dict = {"type": "string"} # extent.spatial.crs
|
|
535
|
-
trs_schema: dict = {"type": "string"} # extent.temporal.trs
|
|
536
|
-
vrs_schema: dict = {"type": "string"} # extent.vertical.vrs
|
|
537
|
-
supported_crs_schema: dict = {"type": "string"} # crs[] and crs_details[].crs
|
|
538
|
-
|
|
539
|
-
if profile and profile.extent_requirements:
|
|
540
|
-
er = profile.extent_requirements
|
|
541
|
-
|
|
542
|
-
def _apply(schema: dict, constraint: "object | None") -> None:
|
|
543
|
-
if constraint is None:
|
|
544
|
-
return
|
|
545
|
-
if getattr(constraint, "allowed", None):
|
|
546
|
-
schema["enum"] = constraint.allowed
|
|
547
|
-
elif getattr(constraint, "pattern", None):
|
|
548
|
-
schema["pattern"] = constraint.pattern
|
|
549
|
-
|
|
550
|
-
_apply(crs_schema, er.extent_crs)
|
|
551
|
-
_apply(trs_schema, er.extent_trs)
|
|
552
|
-
_apply(vrs_schema, er.extent_vrs)
|
|
553
|
-
_apply(supported_crs_schema, er.supported_crs)
|
|
554
|
-
|
|
555
|
-
# Build parameter_names schema. Precedence: per-collection parameter_schema
|
|
556
|
-
# override → profile-level parameter_schema → default EDR Parameter schema.
|
|
557
|
-
coll_param_schema = getattr(coll, "parameter_schema", None)
|
|
558
|
-
if coll_param_schema:
|
|
559
|
-
import copy
|
|
560
|
-
param_item_schema = copy.deepcopy(coll_param_schema)
|
|
561
|
-
elif profile and profile.parameter_schema:
|
|
562
|
-
# Deep-copy so we don't mutate the profile model
|
|
563
|
-
import copy
|
|
564
|
-
param_item_schema = copy.deepcopy(profile.parameter_schema)
|
|
565
|
-
else:
|
|
566
|
-
param_item_schema = _parameter_schema(profile.openapi_version)
|
|
567
|
-
|
|
568
|
-
param_names_schema: dict = {
|
|
569
|
-
"type": "object",
|
|
570
|
-
"additionalProperties": param_item_schema,
|
|
571
|
-
}
|
|
572
|
-
if coll.parameter_names:
|
|
573
|
-
param_names_schema["properties"] = {
|
|
574
|
-
name: param_item_schema for name in coll.parameter_names.root
|
|
575
|
-
}
|
|
576
|
-
if version == "3.1.0" and profile and profile.parameter_name_pattern:
|
|
577
|
-
param_names_schema["propertyNames"] = {
|
|
578
|
-
"type": "string",
|
|
579
|
-
"pattern": profile.parameter_name_pattern,
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
# Build output_formats enum from collection's declared formats
|
|
583
|
-
output_formats_schema: dict = {"type": "array", "items": {"type": "string"}}
|
|
584
|
-
if coll.output_formats:
|
|
585
|
-
output_formats_schema["items"] = {"type": "string", "enum": list(coll.output_formats)}
|
|
586
|
-
|
|
587
|
-
# Build crs array schema — use collection-level crs list if present,
|
|
588
|
-
# otherwise apply supported_crs constraint from extent_requirements.
|
|
589
|
-
# supported_crs is distinct from extent_crs: it constrains what the
|
|
590
|
-
# service supports for queries, not how the extent itself is expressed.
|
|
591
|
-
if coll.crs:
|
|
592
|
-
crs_array_schema: dict = {"type": "array", "items": {"type": "string", "enum": list(coll.crs)}}
|
|
593
|
-
else:
|
|
594
|
-
crs_array_schema = {"type": "array", "items": supported_crs_schema}
|
|
595
|
-
|
|
596
|
-
schema: dict = {
|
|
597
|
-
"type": "object",
|
|
598
|
-
"required": ["id", "links", "extent"],
|
|
599
|
-
"properties": {
|
|
600
|
-
"id": {"type": "string"},
|
|
601
|
-
"title": {"type": "string"},
|
|
602
|
-
"description": {"type": "string"},
|
|
603
|
-
"keywords": {"type": "array", "items": {"type": "string"}},
|
|
604
|
-
"links": _LINKS_ARRAY,
|
|
605
|
-
"crs": crs_array_schema,
|
|
606
|
-
"output_formats": output_formats_schema,
|
|
607
|
-
"parameter_names": param_names_schema,
|
|
608
|
-
"extent": {
|
|
609
|
-
"type": "object",
|
|
610
|
-
"required": ["spatial"],
|
|
611
|
-
"properties": {
|
|
612
|
-
"spatial": {
|
|
613
|
-
"type": "object",
|
|
614
|
-
"required": ["bbox", "crs"],
|
|
615
|
-
"properties": {
|
|
616
|
-
"bbox": {
|
|
617
|
-
"type": "array",
|
|
618
|
-
"items": {
|
|
619
|
-
"type": "array",
|
|
620
|
-
"items": {"type": "number"},
|
|
621
|
-
"minItems": 4,
|
|
622
|
-
"maxItems": 6,
|
|
623
|
-
},
|
|
624
|
-
},
|
|
625
|
-
"crs": crs_schema,
|
|
626
|
-
},
|
|
627
|
-
},
|
|
628
|
-
"temporal": {
|
|
629
|
-
"type": "object",
|
|
630
|
-
"properties": {
|
|
631
|
-
"interval": {
|
|
632
|
-
"type": "array",
|
|
633
|
-
"items": {
|
|
634
|
-
"type": "array",
|
|
635
|
-
"items": {"type": "string", "format": "date-time", "nullable": True} if version == "3.0.0" else {
|
|
636
|
-
"oneOf": [
|
|
637
|
-
{"type": "string", "format": "date-time"},
|
|
638
|
-
{"type": "null"},
|
|
639
|
-
]
|
|
640
|
-
},
|
|
641
|
-
"minItems": 2,
|
|
642
|
-
"maxItems": 2,
|
|
643
|
-
},
|
|
644
|
-
},
|
|
645
|
-
"values": {
|
|
646
|
-
"type": "array",
|
|
647
|
-
"items": {"type": "string"},
|
|
648
|
-
},
|
|
649
|
-
"trs": trs_schema,
|
|
650
|
-
},
|
|
651
|
-
},
|
|
652
|
-
"vertical": {
|
|
653
|
-
"type": "object",
|
|
654
|
-
"properties": {
|
|
655
|
-
"interval": {
|
|
656
|
-
"type": "array",
|
|
657
|
-
"items": {
|
|
658
|
-
"type": "array",
|
|
659
|
-
"items": {"type": "string"},
|
|
660
|
-
},
|
|
661
|
-
},
|
|
662
|
-
"values": {
|
|
663
|
-
"type": "array",
|
|
664
|
-
"items": {"type": "string"},
|
|
665
|
-
},
|
|
666
|
-
"vrs": vrs_schema,
|
|
667
|
-
"positive": {
|
|
668
|
-
"type": "string",
|
|
669
|
-
"enum": ["up", "down"],
|
|
670
|
-
"description": (
|
|
671
|
-
"Direction in which vertical extent values increase "
|
|
672
|
-
"(CF Conventions 'positive' attribute). Disambiguates "
|
|
673
|
-
"interval ordering for VRS such as pressure levels."
|
|
674
|
-
),
|
|
675
|
-
},
|
|
676
|
-
},
|
|
677
|
-
},
|
|
678
|
-
"custom": {
|
|
679
|
-
"type": "array",
|
|
680
|
-
"items": {
|
|
681
|
-
"type": "object",
|
|
682
|
-
"required": ["id", "interval", "reference"],
|
|
683
|
-
"properties": {
|
|
684
|
-
"id": {"type": "string"},
|
|
685
|
-
"interval": {
|
|
686
|
-
"type": "array",
|
|
687
|
-
"items": {"type": "string"},
|
|
688
|
-
},
|
|
689
|
-
"values": {
|
|
690
|
-
"type": "array",
|
|
691
|
-
"items": {"type": "string"},
|
|
692
|
-
},
|
|
693
|
-
"reference": {"type": "string"},
|
|
694
|
-
},
|
|
695
|
-
},
|
|
696
|
-
},
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
"data_queries": {
|
|
700
|
-
"type": "object",
|
|
701
|
-
"description": "Available EDR data query types for this collection",
|
|
702
|
-
"additionalProperties": {
|
|
703
|
-
"type": "object",
|
|
704
|
-
"properties": {
|
|
705
|
-
"link": {
|
|
706
|
-
"type": "object",
|
|
707
|
-
"properties": {
|
|
708
|
-
"href": {"type": "string"},
|
|
709
|
-
"rel": {"type": "string"},
|
|
710
|
-
"variables": {
|
|
711
|
-
"type": "object",
|
|
712
|
-
"properties": {
|
|
713
|
-
"query_type": {"type": "string"},
|
|
714
|
-
"output_formats": {
|
|
715
|
-
"type": "array",
|
|
716
|
-
"items": {"type": "string"},
|
|
717
|
-
},
|
|
718
|
-
"default_output_format": {
|
|
719
|
-
"type": "string",
|
|
720
|
-
"description": "Default output format for this data query when f is omitted",
|
|
721
|
-
},
|
|
722
|
-
"crs": {
|
|
723
|
-
"type": "array",
|
|
724
|
-
"description": "CRS values supported by this data query (shorthand for crs_details)",
|
|
725
|
-
"items": {"type": "string"},
|
|
726
|
-
},
|
|
727
|
-
"crs_details": {
|
|
728
|
-
"type": "array",
|
|
729
|
-
"description": "CRS values supported by this data query",
|
|
730
|
-
"items": {
|
|
731
|
-
"type": "object",
|
|
732
|
-
"required": ["crs"],
|
|
733
|
-
"properties": {
|
|
734
|
-
"crs": supported_crs_schema,
|
|
735
|
-
"wkt": {"type": "string"},
|
|
736
|
-
},
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
|
-
},
|
|
740
|
-
},
|
|
741
|
-
},
|
|
742
|
-
},
|
|
743
|
-
},
|
|
744
|
-
},
|
|
745
|
-
},
|
|
746
|
-
},
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
return {
|
|
750
|
-
"description": "Collection metadata",
|
|
751
|
-
"content": {"application/json": {"schema": schema}},
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
519
|
def _parameter_schema(version: str) -> dict:
|
|
756
520
|
"""OpenAPI schema for a single EDR Parameter object."""
|
|
757
521
|
return {
|
|
@@ -806,6 +570,163 @@ def _parameter_schema(version: str) -> dict:
|
|
|
806
570
|
}
|
|
807
571
|
|
|
808
572
|
|
|
573
|
+
def _parameter_instance_schema(param: object, version: str) -> dict:
|
|
574
|
+
"""Build a fixed OpenAPI schema for a specific Parameter instance."""
|
|
575
|
+
schema = {
|
|
576
|
+
"type": "object",
|
|
577
|
+
"required": ["type", "observedProperty"],
|
|
578
|
+
"properties": {
|
|
579
|
+
"type": {"type": "string", **_const("Parameter", version)},
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
for field in ("id", "label", "description", "data-type"):
|
|
583
|
+
val = getattr(param, field.replace("-", "_"), None)
|
|
584
|
+
if val:
|
|
585
|
+
schema["properties"][field] = {"type": "string", **_const(val, version)}
|
|
586
|
+
|
|
587
|
+
op = getattr(param, "observedProperty", None)
|
|
588
|
+
if op:
|
|
589
|
+
op_props = {"label": {"type": "string", **_const(op.label, version)}}
|
|
590
|
+
if getattr(op, "id", None):
|
|
591
|
+
op_props["id"] = {"type": "string", **_const(op.id, version)}
|
|
592
|
+
if getattr(op, "description", None):
|
|
593
|
+
op_props["description"] = {"type": "string", **_const(op.description, version)}
|
|
594
|
+
schema["properties"]["observedProperty"] = {"type": "object", "properties": op_props, "required": ["label"]}
|
|
595
|
+
|
|
596
|
+
u = getattr(param, "unit", None)
|
|
597
|
+
if u:
|
|
598
|
+
u_props = {}
|
|
599
|
+
if getattr(u, "label", None):
|
|
600
|
+
u_props["label"] = {"type": "string", **_const(u.label, version)}
|
|
601
|
+
symbol = getattr(u, "symbol", None)
|
|
602
|
+
if symbol:
|
|
603
|
+
if isinstance(symbol, str):
|
|
604
|
+
u_props["symbol"] = {"type": "string", **_const(symbol, version)}
|
|
605
|
+
elif hasattr(symbol, "value") and hasattr(symbol, "type"):
|
|
606
|
+
u_props["symbol"] = {
|
|
607
|
+
"type": "object",
|
|
608
|
+
"required": ["value", "type"],
|
|
609
|
+
"properties": {
|
|
610
|
+
"value": {"type": "string", **_const(symbol.value, version)},
|
|
611
|
+
"type": {"type": "string", **_const(symbol.type, version)}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
schema["properties"]["unit"] = {"type": "object", "properties": u_props}
|
|
615
|
+
|
|
616
|
+
return schema
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def _collection_response_schema(coll: Collection,
|
|
620
|
+
profile: "ServiceProfile | None") -> dict:
|
|
621
|
+
"""Build a 200 response schema for a single collection endpoint."""
|
|
622
|
+
version = profile.openapi_version if profile else "3.0.0"
|
|
623
|
+
crs_schema: dict = {"type": "string"}
|
|
624
|
+
trs_schema: dict = {"type": "string"}
|
|
625
|
+
vrs_schema: dict = {"type": "string"}
|
|
626
|
+
supported_crs_schema: dict = {"type": "string"}
|
|
627
|
+
|
|
628
|
+
if profile and profile.extent_requirements:
|
|
629
|
+
er = profile.extent_requirements
|
|
630
|
+
def _apply(schema: dict, constraint: "object | None") -> None:
|
|
631
|
+
if constraint is None: return
|
|
632
|
+
if getattr(constraint, "allowed", None): schema["enum"] = constraint.allowed
|
|
633
|
+
elif getattr(constraint, "pattern", None): schema["pattern"] = constraint.pattern
|
|
634
|
+
_apply(crs_schema, er.extent_crs)
|
|
635
|
+
_apply(trs_schema, er.extent_trs)
|
|
636
|
+
_apply(vrs_schema, er.extent_vrs)
|
|
637
|
+
_apply(supported_crs_schema, er.supported_crs)
|
|
638
|
+
|
|
639
|
+
coll_param_schema = getattr(coll, "parameter_schema", None)
|
|
640
|
+
if coll_param_schema:
|
|
641
|
+
import copy
|
|
642
|
+
param_item_schema = copy.deepcopy(coll_param_schema)
|
|
643
|
+
elif profile and profile.parameter_schema:
|
|
644
|
+
import copy
|
|
645
|
+
param_item_schema = copy.deepcopy(profile.parameter_schema)
|
|
646
|
+
else:
|
|
647
|
+
param_item_schema = _parameter_schema(profile.openapi_version)
|
|
648
|
+
|
|
649
|
+
param_names_schema: dict = {"type": "object", "additionalProperties": param_item_schema}
|
|
650
|
+
if coll.parameter_names:
|
|
651
|
+
param_names_schema["properties"] = {
|
|
652
|
+
name: _parameter_instance_schema(param, version)
|
|
653
|
+
for name, param in coll.parameter_names.root.items()
|
|
654
|
+
}
|
|
655
|
+
if version == "3.1.0" and profile and profile.parameter_name_pattern:
|
|
656
|
+
param_names_schema["propertyNames"] = {"type": "string", "pattern": profile.parameter_name_pattern}
|
|
657
|
+
|
|
658
|
+
output_formats_schema: dict = {"type": "array", "items": {"type": "string"}}
|
|
659
|
+
if coll.output_formats:
|
|
660
|
+
output_formats_schema["items"] = {"type": "string", "enum": list(coll.output_formats)}
|
|
661
|
+
|
|
662
|
+
if coll.crs: crs_array_schema: dict = {"type": "array", "items": {"type": "string", "enum": list(coll.crs)}}
|
|
663
|
+
else: crs_array_schema = {"type": "array", "items": supported_crs_schema}
|
|
664
|
+
|
|
665
|
+
extent_props: dict = {}
|
|
666
|
+
if coll.extent and coll.extent.spatial:
|
|
667
|
+
s = coll.extent.spatial
|
|
668
|
+
extent_props["spatial"] = {
|
|
669
|
+
"type": "object", "required": ["bbox", "crs"],
|
|
670
|
+
"properties": {
|
|
671
|
+
"bbox": {"type": "array", "items": {"type": "array", "items": {"type": "number"}}, **_const(s.bbox, version)},
|
|
672
|
+
"crs": {"type": "string", **_const(s.crs, version)}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if coll.extent and coll.extent.temporal:
|
|
676
|
+
t = coll.extent.temporal
|
|
677
|
+
temporal_props = {"trs": {"type": "string", **_const(t.trs, version)}}
|
|
678
|
+
if t.interval: temporal_props["interval"] = {"type": "array", **_const(t.interval, version)}
|
|
679
|
+
if t.values: temporal_props["values"] = {"type": "array", **_const(t.values, version)}
|
|
680
|
+
extent_props["temporal"] = {"type": "object", "required": ["trs"], "properties": temporal_props}
|
|
681
|
+
if coll.extent and coll.extent.vertical:
|
|
682
|
+
v = coll.extent.vertical
|
|
683
|
+
vertical_props = {"vrs": {"type": "string", **_const(v.vrs, version)}}
|
|
684
|
+
if v.interval: vertical_props["interval"] = {"type": "array", **_const(v.interval, version)}
|
|
685
|
+
if v.values: vertical_props["values"] = {"type": "array", **_const(v.values, version)}
|
|
686
|
+
if getattr(v, "positive", None): vertical_props["positive"] = {"type": "string", **_const(v.positive, version)}
|
|
687
|
+
extent_props["vertical"] = {"type": "object", "required": ["vrs"], "properties": vertical_props}
|
|
688
|
+
if coll.extent and coll.extent.custom:
|
|
689
|
+
custom_items = [{"type": "object", "required": ["id", "interval", "reference"],
|
|
690
|
+
"properties": {"id": {"type": "string", **_const(c.id, version)},
|
|
691
|
+
"interval": {"type": "array", **_const(c.interval, version)},
|
|
692
|
+
"reference": {"type": "string", **_const(c.reference, version)}}} for c in coll.extent.custom]
|
|
693
|
+
extent_props["custom"] = {"type": "array", "items": {"oneOf": custom_items}}
|
|
694
|
+
|
|
695
|
+
dq_props = {}
|
|
696
|
+
if coll.data_queries:
|
|
697
|
+
for dq_name, dq_val in coll.data_queries:
|
|
698
|
+
if dq_val is None: continue
|
|
699
|
+
vars_props = {}
|
|
700
|
+
vars_obj = dq_val.link.variables
|
|
701
|
+
if vars_obj:
|
|
702
|
+
for f in ("query_type", "default_output_format"):
|
|
703
|
+
v = getattr(vars_obj, f, None);
|
|
704
|
+
if v: vars_props[f] = {"type": "string", **_const(v, version)}
|
|
705
|
+
if getattr(vars_obj, "output_formats", None):
|
|
706
|
+
vars_props["output_formats"] = {"type": "array", "items": {"type": "string", "enum": list(vars_obj.output_formats)}}
|
|
707
|
+
extra = getattr(vars_obj, "model_extra", {}) or {}
|
|
708
|
+
if "crs" in extra: vars_props["crs"] = {"type": "array", "items": {"type": "string"}, **_const(extra["crs"], version)}
|
|
709
|
+
if "crs_details" in extra: vars_props["crs_details"] = {"type": "array", **_const(extra["crs_details"], version)}
|
|
710
|
+
dq_props[dq_name] = {"type": "object", "properties": {"link": {"type": "object", "properties": {
|
|
711
|
+
"href": {"type": "string", **_const(dq_val.link.href, version)},
|
|
712
|
+
"rel": {"type": "string", **_const(dq_val.link.rel, version)},
|
|
713
|
+
"variables": {"type": "object", "properties": vars_props}
|
|
714
|
+
}}}}
|
|
715
|
+
|
|
716
|
+
schema: dict = {
|
|
717
|
+
"type": "object", "required": ["id", "links", "extent"],
|
|
718
|
+
"properties": {
|
|
719
|
+
"id": {"type": "string", **_const(coll.id, version)},
|
|
720
|
+
"title": {"type": "string", **_const(coll.title, version)} if coll.title else {"type": "string"},
|
|
721
|
+
"description": {"type": "string", **_const(coll.description, version)} if coll.description else {"type": "string"},
|
|
722
|
+
"keywords": {"type": "array", "items": {"type": "string", "enum": coll.keywords}} if coll.keywords else {"type": "array"},
|
|
723
|
+
"links": _LINKS_ARRAY, "crs": crs_array_schema, "output_formats": output_formats_schema,
|
|
724
|
+
"parameter_names": param_names_schema,
|
|
725
|
+
"extent": {"type": "object", "required": ["spatial"], "properties": extent_props},
|
|
726
|
+
"data_queries": {"type": "object", "properties": dq_props}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return {"description": "Collection metadata", "content": {"application/json": {"schema": schema}}}
|
|
809
730
|
def _f_enum(profile: "ServiceProfile | None") -> list[str]:
|
|
810
731
|
"""Allowed values for the `f` (output format) parameter: media types."""
|
|
811
732
|
enum = ["json", "html"]
|
|
@@ -864,6 +785,40 @@ def _f_param(profile: "ServiceProfile | None", variables: object | None) -> dict
|
|
|
864
785
|
}
|
|
865
786
|
|
|
866
787
|
|
|
788
|
+
|
|
789
|
+
def _params_for_query(params: list[dict], profile: "ServiceProfile | None",
|
|
790
|
+
coll: Collection, variables: object | None) -> list[dict]:
|
|
791
|
+
"""Refine query parameters based on collection capabilities (z filtering, crs enum)."""
|
|
792
|
+
import copy
|
|
793
|
+
out = []
|
|
794
|
+
has_vertical = coll.extent and coll.extent.vertical
|
|
795
|
+
supported_crs = list(coll.crs) if coll.crs else []
|
|
796
|
+
|
|
797
|
+
# Check extent_requirements for supported_crs if not explicit on collection
|
|
798
|
+
if not supported_crs and profile and profile.extent_requirements and profile.extent_requirements.supported_crs:
|
|
799
|
+
supported_crs = profile.extent_requirements.supported_crs.allowed or []
|
|
800
|
+
|
|
801
|
+
for p in params:
|
|
802
|
+
if p is _Z and not has_vertical:
|
|
803
|
+
continue
|
|
804
|
+
|
|
805
|
+
# Make a copy if we need to modify (e.g. for f default or crs enum)
|
|
806
|
+
p_eff = p
|
|
807
|
+
|
|
808
|
+
# Handle f parameter default
|
|
809
|
+
if p is _F:
|
|
810
|
+
p_eff = _f_param(profile, variables)
|
|
811
|
+
|
|
812
|
+
# Handle crs parameter enum
|
|
813
|
+
elif p is _CRS and supported_crs:
|
|
814
|
+
p_eff = copy.deepcopy(p)
|
|
815
|
+
if "schema" not in p_eff: p_eff["schema"] = {}
|
|
816
|
+
p_eff["schema"]["type"] = "string"
|
|
817
|
+
p_eff["schema"]["enum"] = supported_crs
|
|
818
|
+
|
|
819
|
+
out.append(p_eff)
|
|
820
|
+
return out
|
|
821
|
+
|
|
867
822
|
def _params_with_default_f(params: list[dict], profile: "ServiceProfile | None",
|
|
868
823
|
variables: object | None) -> list[dict]:
|
|
869
824
|
"""Replace the shared `_F` reference in a params list with a query-specific default."""
|
|
@@ -978,7 +933,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
978
933
|
return qv.link.variables if qv is not None and qv.link else None
|
|
979
934
|
|
|
980
935
|
for qt in active:
|
|
981
|
-
params =
|
|
936
|
+
params = _params_for_query(_QUERY_PARAMS.get(qt, []), profile, coll, _vars(qt))
|
|
982
937
|
|
|
983
938
|
if qt == "instances":
|
|
984
939
|
paths[f"{base}/instances"] = {"get": {
|
|
@@ -1010,7 +965,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1010
965
|
}}
|
|
1011
966
|
# instance-level query sub-paths — GET + optional POST
|
|
1012
967
|
for sub_qt in (active - {"instances"}):
|
|
1013
|
-
sub_params =
|
|
968
|
+
sub_params = _params_for_query(_QUERY_PARAMS.get(sub_qt, []), profile, coll, _vars(sub_qt))
|
|
1014
969
|
if sub_qt == "items":
|
|
1015
970
|
sub_params = _with_paging_limit(sub_params, profile)
|
|
1016
971
|
get_op = {
|
|
@@ -1023,7 +978,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1023
978
|
}
|
|
1024
979
|
path_ops = {"get": get_op}
|
|
1025
980
|
if include_post:
|
|
1026
|
-
path_ops["post"] = _post_operation(get_op, sub_qt, coll
|
|
981
|
+
path_ops["post"] = _post_operation(get_op, sub_qt, coll, cov_resp)
|
|
1027
982
|
paths[f"{base}/instances/{{instanceId}}/{sub_qt}"] = path_ops
|
|
1028
983
|
|
|
1029
984
|
elif qt == "items":
|
|
@@ -1038,7 +993,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1038
993
|
}
|
|
1039
994
|
path_ops = {"get": get_op}
|
|
1040
995
|
if include_post:
|
|
1041
|
-
path_ops["post"] = _post_operation(get_op, "items", coll
|
|
996
|
+
path_ops["post"] = _post_operation(get_op, "items", coll, cov_resp)
|
|
1042
997
|
paths[f"{base}/items"] = path_ops
|
|
1043
998
|
|
|
1044
999
|
elif qt == "locations":
|
|
@@ -1057,7 +1012,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1057
1012
|
}
|
|
1058
1013
|
path_ops = {"get": get_locations_op}
|
|
1059
1014
|
if include_post:
|
|
1060
|
-
path_ops["post"] = _post_operation(get_locations_op, "locations", coll
|
|
1015
|
+
path_ops["post"] = _post_operation(get_locations_op, "locations", coll, locations_responses)
|
|
1061
1016
|
paths[f"{base}/locations"] = path_ops
|
|
1062
1017
|
|
|
1063
1018
|
get_location_op = {
|
|
@@ -1073,7 +1028,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1073
1028
|
}
|
|
1074
1029
|
loc_path_ops = {"get": get_location_op}
|
|
1075
1030
|
if include_post:
|
|
1076
|
-
loc_path_ops["post"] = _post_operation(get_location_op, "locations", coll
|
|
1031
|
+
loc_path_ops["post"] = _post_operation(get_location_op, "locations", coll, cov_resp)
|
|
1077
1032
|
paths[f"{base}/locations/{{locId}}"] = loc_path_ops
|
|
1078
1033
|
|
|
1079
1034
|
else:
|
|
@@ -1087,27 +1042,28 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1087
1042
|
}
|
|
1088
1043
|
path_ops = {"get": get_op}
|
|
1089
1044
|
if include_post:
|
|
1090
|
-
path_ops["post"] = _post_operation(get_op, qt, coll
|
|
1045
|
+
path_ops["post"] = _post_operation(get_op, qt, coll, cov_resp)
|
|
1091
1046
|
paths[f"{base}/{qt}"] = path_ops
|
|
1092
1047
|
|
|
1093
1048
|
return paths
|
|
1094
1049
|
|
|
1095
1050
|
|
|
1096
1051
|
def _core_paths(profile: ServiceProfile) -> dict:
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
title=profile.title,
|
|
1100
|
-
description=profile.description or "",
|
|
1101
|
-
keywords=profile.keywords or None,
|
|
1102
|
-
)
|
|
1052
|
+
version = profile.openapi_version
|
|
1053
|
+
landing_response = _landing_page_schema(profile)
|
|
1103
1054
|
|
|
1104
1055
|
# Conformance response with required conformance classes
|
|
1105
|
-
conformance_response =
|
|
1056
|
+
conformance_response = copy.deepcopy(_R200_CONFORMANCE)
|
|
1106
1057
|
if profile.required_conformance_classes:
|
|
1107
|
-
|
|
1108
|
-
"
|
|
1109
|
-
|
|
1110
|
-
|
|
1058
|
+
if version == "3.1.0":
|
|
1059
|
+
conformance_response["content"]["application/json"]["schema"]["properties"]["conformsTo"]["contains"] = {
|
|
1060
|
+
"enum": profile.required_conformance_classes
|
|
1061
|
+
}
|
|
1062
|
+
else:
|
|
1063
|
+
conformance_response["content"]["application/json"]["schema"]["properties"]["conformsTo"]["items"] = {
|
|
1064
|
+
"type": "string",
|
|
1065
|
+
"enum": profile.required_conformance_classes
|
|
1066
|
+
}
|
|
1111
1067
|
|
|
1112
1068
|
return {
|
|
1113
1069
|
"/": {"get": {
|
|
@@ -1134,7 +1090,7 @@ def _core_paths(profile: ServiceProfile) -> dict:
|
|
|
1134
1090
|
"parameters": [_F, _LANG],
|
|
1135
1091
|
"responses": {"200": {"description": "Collections list", "content": {"application/json": {"schema": {"type": "object", "properties": {"links": _LINKS_ARRAY}}}}}, "400": _ERR_400, "500": _ERR_500},
|
|
1136
1092
|
}},
|
|
1137
|
-
"/
|
|
1093
|
+
"/api": {"get": {
|
|
1138
1094
|
"summary": "OpenAPI definition",
|
|
1139
1095
|
"description": "Retrieve the OpenAPI definition document for this API.",
|
|
1140
1096
|
"operationId": "getOpenAPI",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cite_features.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/compile.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.2 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|