oapi-profile-builder 3.6.3__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.3/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.7.0}/PKG-INFO +1 -1
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/pyproject.toml +1 -1
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/generate.py +292 -343
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0/src/oapi_profile_builder.egg-info}/PKG-INFO +1 -1
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/LICENSE +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/README.md +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/setup.cfg +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/__init__.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cite.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cite_features.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cli.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/compile.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/cover.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/models.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/server_validation.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
- {oapi_profile_builder-3.6.3 → 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.3 → 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": {
|
|
@@ -272,6 +299,8 @@ def _coverage_response(coll: Collection, profile: "ServiceProfile | None") -> di
|
|
|
272
299
|
coll_formats: list[str] = []
|
|
273
300
|
if coll.output_formats:
|
|
274
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]
|
|
275
304
|
|
|
276
305
|
content: dict = {}
|
|
277
306
|
if coll_formats:
|
|
@@ -279,15 +308,8 @@ def _coverage_response(coll: Collection, profile: "ServiceProfile | None") -> di
|
|
|
279
308
|
schema_ref = fmt_map.get(fmt_name)
|
|
280
309
|
if fmt_name == "CoverageJSON":
|
|
281
310
|
media_type = "application/prs.coverage+json"
|
|
282
|
-
|
|
283
|
-
alt_media = "application/vnd.cov+json"
|
|
284
|
-
if schema_ref:
|
|
285
|
-
schema = {"$ref": schema_ref}
|
|
286
|
-
else:
|
|
287
|
-
# Fallback to an inline object if external ref is problematic
|
|
288
|
-
schema = {"type": "object"}
|
|
311
|
+
schema = {"$ref": schema_ref} if schema_ref else {"type": "object"}
|
|
289
312
|
content[media_type] = {"schema": schema}
|
|
290
|
-
content[alt_media] = {"schema": schema}
|
|
291
313
|
elif fmt_name == "GeoJSON":
|
|
292
314
|
media_type = "application/geo+json"
|
|
293
315
|
schema = {"$ref": schema_ref} if schema_ref else {"type": "object"}
|
|
@@ -302,11 +324,11 @@ def _coverage_response(coll: Collection, profile: "ServiceProfile | None") -> di
|
|
|
302
324
|
schema = {"$ref": pf.schema_ref} if pf.schema_ref else {"type": "object"}
|
|
303
325
|
content[pf.media_type] = {"schema": schema}
|
|
304
326
|
break
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
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"}}
|
|
310
332
|
|
|
311
333
|
# Per the EDR error-handling requirement, a query whose spatio-temporal
|
|
312
334
|
# selection falls inside the collection extent but matches no data returns
|
|
@@ -331,8 +353,9 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
331
353
|
"required": ["coords"],
|
|
332
354
|
"properties": {
|
|
333
355
|
"coords": {"type": "string", "description": "WKT Point geometry (e.g. POINT(0 51.5))"},
|
|
334
|
-
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval
|
|
356
|
+
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
335
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"},
|
|
336
359
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
337
360
|
"f": {"type": "string", "description": "Response format"},
|
|
338
361
|
},
|
|
@@ -344,6 +367,7 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
344
367
|
"coords": {"type": "string", "description": "WKT Polygon geometry"},
|
|
345
368
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
346
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"},
|
|
347
371
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
348
372
|
"f": {"type": "string", "description": "Response format"},
|
|
349
373
|
},
|
|
@@ -357,6 +381,7 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
357
381
|
"within-units": {"type": "string", "description": "Units for the within parameter (e.g. km)"},
|
|
358
382
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
359
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"},
|
|
360
385
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
361
386
|
"f": {"type": "string", "description": "Response format"},
|
|
362
387
|
},
|
|
@@ -374,6 +399,7 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
374
399
|
},
|
|
375
400
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
376
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"},
|
|
377
403
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
378
404
|
"f": {"type": "string", "description": "Response format"},
|
|
379
405
|
},
|
|
@@ -385,6 +411,7 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
385
411
|
"coords": {"type": "string", "description": "WKT LineString geometry"},
|
|
386
412
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
387
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"},
|
|
388
415
|
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
389
416
|
"f": {"type": "string", "description": "Response format"},
|
|
390
417
|
},
|
|
@@ -397,41 +424,22 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
397
424
|
"corridor-width": {"type": "number", "description": "Width of the corridor"},
|
|
398
425
|
"corridor-height": {"type": "number", "description": "Height of the corridor"},
|
|
399
426
|
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
400
|
-
"
|
|
401
|
-
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
402
|
-
"f": {"type": "string", "description": "Response format"},
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
"items": {
|
|
406
|
-
"type": "object",
|
|
407
|
-
"properties": {
|
|
408
|
-
"bbox": {"type": "string", "description": "Bounding box filter"},
|
|
409
|
-
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
410
|
-
"parameter-name": {"type": "string", "description": "Comma-separated list of parameter names to return"},
|
|
411
|
-
"z": {"type": "string", "description": "Vertical level(s)"},
|
|
427
|
+
"crs": {"type": "string", "description": "Identifier for the coordinate reference system"},
|
|
412
428
|
"f": {"type": "string", "description": "Response format"},
|
|
413
429
|
},
|
|
414
|
-
},
|
|
415
|
-
"locations": {
|
|
416
|
-
"type": "object",
|
|
417
|
-
"properties": {
|
|
418
|
-
"bbox": {
|
|
419
|
-
"type": "array",
|
|
420
|
-
"description": "Bounding box [minLon, minLat, maxLon, maxLat]",
|
|
421
|
-
"items": {"type": "number"},
|
|
422
|
-
"minItems": 4,
|
|
423
|
-
"maxItems": 6,
|
|
424
|
-
},
|
|
425
|
-
"datetime": {"type": "string", "description": "RFC 3339 datetime or interval"},
|
|
426
|
-
"f": {"type": "string", "description": "Response format"},
|
|
427
|
-
},
|
|
428
|
-
},
|
|
429
430
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
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:
|
|
433
433
|
"""Build a POST operation mirroring a GET EDR data query operation."""
|
|
434
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
|
+
|
|
435
443
|
# Carry over a default output format from the GET operation's `f` parameter
|
|
436
444
|
# so the POST body documents the same default.
|
|
437
445
|
default_f = None
|
|
@@ -440,8 +448,6 @@ def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict
|
|
|
440
448
|
default_f = p.get("schema", {}).get("default")
|
|
441
449
|
break
|
|
442
450
|
if default_f and "properties" in body_schema and "f" in body_schema["properties"]:
|
|
443
|
-
import copy
|
|
444
|
-
body_schema = copy.deepcopy(body_schema)
|
|
445
451
|
body_schema["properties"]["f"]["default"] = default_f
|
|
446
452
|
return {
|
|
447
453
|
"summary": get_op["summary"].replace("query", "query (POST)"),
|
|
@@ -465,35 +471,35 @@ def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict
|
|
|
465
471
|
_QUERY_PARAMS: dict[str, list[dict]] = {
|
|
466
472
|
"position": [
|
|
467
473
|
{"$ref": f"{_EDR}/parameters/positionCoords.yaml"},
|
|
468
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
474
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
469
475
|
],
|
|
470
476
|
"area": [
|
|
471
477
|
{"$ref": f"{_EDR}/parameters/areaCoords.yaml"},
|
|
472
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
478
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
473
479
|
],
|
|
474
480
|
"radius": [
|
|
475
481
|
{"$ref": f"{_EDR}/parameters/positionCoords.yaml"},
|
|
476
482
|
{"$ref": f"{_EDR}/parameters/within.yaml"},
|
|
477
483
|
{"name": "within-units", "in": "query", "required": True, "description": "Units for the within parameter e.g. km", "schema": {"type": "string"}},
|
|
478
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
484
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
479
485
|
],
|
|
480
486
|
"cube": [
|
|
481
487
|
{"$ref": f"{_EDR}/parameters/bbox.yaml"},
|
|
482
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
488
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
483
489
|
],
|
|
484
490
|
"trajectory": [
|
|
485
491
|
{"name": "coords", "in": "query", "required": True, "description": "WKT LineString geometry", "schema": {"type": "string"}},
|
|
486
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
492
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
487
493
|
],
|
|
488
494
|
"corridor": [
|
|
489
495
|
{"name": "coords", "in": "query", "required": True, "description": "WKT LineString geometry", "schema": {"type": "string"}},
|
|
490
496
|
{"name": "corridor-width", "in": "query", "required": True, "description": "Width of the corridor", "schema": {"type": "number"}},
|
|
491
497
|
{"name": "corridor-height", "in": "query", "description": "Height of the corridor", "schema": {"type": "number"}},
|
|
492
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
498
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
493
499
|
],
|
|
494
500
|
"items": [
|
|
495
501
|
{"name": "bbox", "in": "query", "description": "Bounding box filter", "schema": {"type": "string"}},
|
|
496
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
502
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
497
503
|
],
|
|
498
504
|
"locations": [
|
|
499
505
|
{"$ref": f"{_EDR}/parameters/bbox.yaml"},
|
|
@@ -502,257 +508,14 @@ _QUERY_PARAMS: dict[str, list[dict]] = {
|
|
|
502
508
|
"instances": [_F],
|
|
503
509
|
}
|
|
504
510
|
|
|
505
|
-
|
|
506
511
|
def _operation_id(*parts: str) -> str:
|
|
507
|
-
"""Build a valid operationId (letters, digits, underscores only).
|
|
508
|
-
|
|
509
|
-
Each part is sanitized by splitting on non-alphanumeric characters,
|
|
510
|
-
capitalizing the first letter of each word while preserving the rest,
|
|
511
|
-
then joining everything together.
|
|
512
|
-
"""
|
|
513
512
|
import re as _re
|
|
514
|
-
|
|
515
513
|
def sanitize(s: str) -> str:
|
|
516
514
|
words = _re.split(r"[^a-zA-Z0-9]+", s)
|
|
517
515
|
return "".join((w[0].upper() + w[1:]) for w in words if w)
|
|
518
|
-
|
|
519
516
|
return "".join(sanitize(p) for p in parts)
|
|
520
517
|
|
|
521
518
|
|
|
522
|
-
def _collection_response_schema(coll: Collection,
|
|
523
|
-
profile: "ServiceProfile | None") -> dict:
|
|
524
|
-
"""Build a 200 response schema for a single collection endpoint.
|
|
525
|
-
|
|
526
|
-
Includes:
|
|
527
|
-
- CRS constraints from extent_requirements (enum or pattern)
|
|
528
|
-
- TRS/VRS constraints from extent_requirements
|
|
529
|
-
- Full temporal extent schema (interval, values, trs)
|
|
530
|
-
- Collection-level crs array
|
|
531
|
-
- Full parameter_names objects with all fields
|
|
532
|
-
- parameter_name_pattern as propertyNames constraint
|
|
533
|
-
"""
|
|
534
|
-
version = profile.openapi_version if profile else "3.0.0"
|
|
535
|
-
crs_schema: dict = {"type": "string"} # extent.spatial.crs
|
|
536
|
-
trs_schema: dict = {"type": "string"} # extent.temporal.trs
|
|
537
|
-
vrs_schema: dict = {"type": "string"} # extent.vertical.vrs
|
|
538
|
-
supported_crs_schema: dict = {"type": "string"} # crs[] and crs_details[].crs
|
|
539
|
-
|
|
540
|
-
if profile and profile.extent_requirements:
|
|
541
|
-
er = profile.extent_requirements
|
|
542
|
-
|
|
543
|
-
def _apply(schema: dict, constraint: "object | None") -> None:
|
|
544
|
-
if constraint is None:
|
|
545
|
-
return
|
|
546
|
-
if getattr(constraint, "allowed", None):
|
|
547
|
-
schema["enum"] = constraint.allowed
|
|
548
|
-
elif getattr(constraint, "pattern", None):
|
|
549
|
-
schema["pattern"] = constraint.pattern
|
|
550
|
-
|
|
551
|
-
_apply(crs_schema, er.extent_crs)
|
|
552
|
-
_apply(trs_schema, er.extent_trs)
|
|
553
|
-
_apply(vrs_schema, er.extent_vrs)
|
|
554
|
-
_apply(supported_crs_schema, er.supported_crs)
|
|
555
|
-
|
|
556
|
-
# Build parameter_names schema. Precedence: per-collection parameter_schema
|
|
557
|
-
# override → profile-level parameter_schema → default EDR Parameter schema.
|
|
558
|
-
coll_param_schema = getattr(coll, "parameter_schema", None)
|
|
559
|
-
if coll_param_schema:
|
|
560
|
-
import copy
|
|
561
|
-
param_item_schema = copy.deepcopy(coll_param_schema)
|
|
562
|
-
elif profile and profile.parameter_schema:
|
|
563
|
-
# Deep-copy so we don't mutate the profile model
|
|
564
|
-
import copy
|
|
565
|
-
param_item_schema = copy.deepcopy(profile.parameter_schema)
|
|
566
|
-
else:
|
|
567
|
-
param_item_schema = _parameter_schema(profile.openapi_version)
|
|
568
|
-
|
|
569
|
-
param_names_schema: dict = {
|
|
570
|
-
"type": "object",
|
|
571
|
-
"additionalProperties": param_item_schema,
|
|
572
|
-
}
|
|
573
|
-
if coll.parameter_names:
|
|
574
|
-
param_names_schema["properties"] = {
|
|
575
|
-
name: param_item_schema for name in coll.parameter_names.root
|
|
576
|
-
}
|
|
577
|
-
if version == "3.1.0" and profile and profile.parameter_name_pattern:
|
|
578
|
-
param_names_schema["propertyNames"] = {
|
|
579
|
-
"type": "string",
|
|
580
|
-
"pattern": profile.parameter_name_pattern,
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
# Build output_formats enum from collection's declared formats
|
|
584
|
-
output_formats_schema: dict = {"type": "array", "items": {"type": "string"}}
|
|
585
|
-
if coll.output_formats:
|
|
586
|
-
output_formats_schema["items"] = {"type": "string", "enum": list(coll.output_formats)}
|
|
587
|
-
|
|
588
|
-
# Build crs array schema — use collection-level crs list if present,
|
|
589
|
-
# otherwise apply supported_crs constraint from extent_requirements.
|
|
590
|
-
# supported_crs is distinct from extent_crs: it constrains what the
|
|
591
|
-
# service supports for queries, not how the extent itself is expressed.
|
|
592
|
-
if coll.crs:
|
|
593
|
-
crs_array_schema: dict = {"type": "array", "items": {"type": "string", "enum": list(coll.crs)}}
|
|
594
|
-
else:
|
|
595
|
-
crs_array_schema = {"type": "array", "items": supported_crs_schema}
|
|
596
|
-
|
|
597
|
-
schema: dict = {
|
|
598
|
-
"type": "object",
|
|
599
|
-
"required": ["id", "links", "extent"],
|
|
600
|
-
"properties": {
|
|
601
|
-
"id": {"type": "string"},
|
|
602
|
-
"title": {"type": "string"},
|
|
603
|
-
"description": {"type": "string"},
|
|
604
|
-
"keywords": {"type": "array", "items": {"type": "string"}},
|
|
605
|
-
"links": _LINKS_ARRAY,
|
|
606
|
-
"crs": crs_array_schema,
|
|
607
|
-
"output_formats": output_formats_schema,
|
|
608
|
-
"parameter_names": param_names_schema,
|
|
609
|
-
"extent": {
|
|
610
|
-
"type": "object",
|
|
611
|
-
"required": ["spatial"],
|
|
612
|
-
"properties": {
|
|
613
|
-
"spatial": {
|
|
614
|
-
"type": "object",
|
|
615
|
-
"required": ["bbox", "crs"],
|
|
616
|
-
"properties": {
|
|
617
|
-
"bbox": {
|
|
618
|
-
"type": "array",
|
|
619
|
-
"items": {
|
|
620
|
-
"type": "array",
|
|
621
|
-
"items": {"type": "number"},
|
|
622
|
-
"minItems": 4,
|
|
623
|
-
"maxItems": 6,
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
"crs": crs_schema,
|
|
627
|
-
},
|
|
628
|
-
},
|
|
629
|
-
"temporal": {
|
|
630
|
-
"type": "object",
|
|
631
|
-
"properties": {
|
|
632
|
-
"interval": {
|
|
633
|
-
"type": "array",
|
|
634
|
-
"items": {
|
|
635
|
-
"type": "array",
|
|
636
|
-
"items": {"type": "string", "format": "date-time", "nullable": True} if version == "3.0.0" else {
|
|
637
|
-
"oneOf": [
|
|
638
|
-
{"type": "string", "format": "date-time"},
|
|
639
|
-
{"type": "null"},
|
|
640
|
-
]
|
|
641
|
-
},
|
|
642
|
-
"minItems": 2,
|
|
643
|
-
"maxItems": 2,
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
"values": {
|
|
647
|
-
"type": "array",
|
|
648
|
-
"items": {"type": "string"},
|
|
649
|
-
},
|
|
650
|
-
"trs": trs_schema,
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
"vertical": {
|
|
654
|
-
"type": "object",
|
|
655
|
-
"properties": {
|
|
656
|
-
"interval": {
|
|
657
|
-
"type": "array",
|
|
658
|
-
"items": {
|
|
659
|
-
"type": "array",
|
|
660
|
-
"items": {"type": "string"},
|
|
661
|
-
},
|
|
662
|
-
},
|
|
663
|
-
"values": {
|
|
664
|
-
"type": "array",
|
|
665
|
-
"items": {"type": "string"},
|
|
666
|
-
},
|
|
667
|
-
"vrs": vrs_schema,
|
|
668
|
-
"positive": {
|
|
669
|
-
"type": "string",
|
|
670
|
-
"enum": ["up", "down"],
|
|
671
|
-
"description": (
|
|
672
|
-
"Direction in which vertical extent values increase "
|
|
673
|
-
"(CF Conventions 'positive' attribute). Disambiguates "
|
|
674
|
-
"interval ordering for VRS such as pressure levels."
|
|
675
|
-
),
|
|
676
|
-
},
|
|
677
|
-
},
|
|
678
|
-
},
|
|
679
|
-
"custom": {
|
|
680
|
-
"type": "array",
|
|
681
|
-
"items": {
|
|
682
|
-
"type": "object",
|
|
683
|
-
"required": ["id", "interval", "reference"],
|
|
684
|
-
"properties": {
|
|
685
|
-
"id": {"type": "string"},
|
|
686
|
-
"interval": {
|
|
687
|
-
"type": "array",
|
|
688
|
-
"items": {"type": "string"},
|
|
689
|
-
},
|
|
690
|
-
"values": {
|
|
691
|
-
"type": "array",
|
|
692
|
-
"items": {"type": "string"},
|
|
693
|
-
},
|
|
694
|
-
"reference": {"type": "string"},
|
|
695
|
-
},
|
|
696
|
-
},
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
},
|
|
700
|
-
"data_queries": {
|
|
701
|
-
"type": "object",
|
|
702
|
-
"description": "Available EDR data query types for this collection",
|
|
703
|
-
"additionalProperties": {
|
|
704
|
-
"type": "object",
|
|
705
|
-
"properties": {
|
|
706
|
-
"link": {
|
|
707
|
-
"type": "object",
|
|
708
|
-
"properties": {
|
|
709
|
-
"href": {"type": "string"},
|
|
710
|
-
"rel": {"type": "string"},
|
|
711
|
-
"variables": {
|
|
712
|
-
"type": "object",
|
|
713
|
-
"properties": {
|
|
714
|
-
"query_type": {"type": "string"},
|
|
715
|
-
"output_formats": {
|
|
716
|
-
"type": "array",
|
|
717
|
-
"items": {"type": "string"},
|
|
718
|
-
},
|
|
719
|
-
"default_output_format": {
|
|
720
|
-
"type": "string",
|
|
721
|
-
"description": "Default output format for this data query when f is omitted",
|
|
722
|
-
},
|
|
723
|
-
"crs": {
|
|
724
|
-
"type": "array",
|
|
725
|
-
"description": "CRS values supported by this data query (shorthand for crs_details)",
|
|
726
|
-
"items": {"type": "string"},
|
|
727
|
-
},
|
|
728
|
-
"crs_details": {
|
|
729
|
-
"type": "array",
|
|
730
|
-
"description": "CRS values supported by this data query",
|
|
731
|
-
"items": {
|
|
732
|
-
"type": "object",
|
|
733
|
-
"required": ["crs"],
|
|
734
|
-
"properties": {
|
|
735
|
-
"crs": supported_crs_schema,
|
|
736
|
-
"wkt": {"type": "string"},
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
|
-
},
|
|
740
|
-
},
|
|
741
|
-
},
|
|
742
|
-
},
|
|
743
|
-
},
|
|
744
|
-
},
|
|
745
|
-
},
|
|
746
|
-
},
|
|
747
|
-
},
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
return {
|
|
751
|
-
"description": "Collection metadata",
|
|
752
|
-
"content": {"application/json": {"schema": schema}},
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
519
|
def _parameter_schema(version: str) -> dict:
|
|
757
520
|
"""OpenAPI schema for a single EDR Parameter object."""
|
|
758
521
|
return {
|
|
@@ -807,6 +570,163 @@ def _parameter_schema(version: str) -> dict:
|
|
|
807
570
|
}
|
|
808
571
|
|
|
809
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}}}
|
|
810
730
|
def _f_enum(profile: "ServiceProfile | None") -> list[str]:
|
|
811
731
|
"""Allowed values for the `f` (output format) parameter: media types."""
|
|
812
732
|
enum = ["json", "html"]
|
|
@@ -865,6 +785,40 @@ def _f_param(profile: "ServiceProfile | None", variables: object | None) -> dict
|
|
|
865
785
|
}
|
|
866
786
|
|
|
867
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
|
+
|
|
868
822
|
def _params_with_default_f(params: list[dict], profile: "ServiceProfile | None",
|
|
869
823
|
variables: object | None) -> list[dict]:
|
|
870
824
|
"""Replace the shared `_F` reference in a params list with a query-specific default."""
|
|
@@ -979,7 +933,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
979
933
|
return qv.link.variables if qv is not None and qv.link else None
|
|
980
934
|
|
|
981
935
|
for qt in active:
|
|
982
|
-
params =
|
|
936
|
+
params = _params_for_query(_QUERY_PARAMS.get(qt, []), profile, coll, _vars(qt))
|
|
983
937
|
|
|
984
938
|
if qt == "instances":
|
|
985
939
|
paths[f"{base}/instances"] = {"get": {
|
|
@@ -1011,7 +965,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1011
965
|
}}
|
|
1012
966
|
# instance-level query sub-paths — GET + optional POST
|
|
1013
967
|
for sub_qt in (active - {"instances"}):
|
|
1014
|
-
sub_params =
|
|
968
|
+
sub_params = _params_for_query(_QUERY_PARAMS.get(sub_qt, []), profile, coll, _vars(sub_qt))
|
|
1015
969
|
if sub_qt == "items":
|
|
1016
970
|
sub_params = _with_paging_limit(sub_params, profile)
|
|
1017
971
|
get_op = {
|
|
@@ -1024,7 +978,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1024
978
|
}
|
|
1025
979
|
path_ops = {"get": get_op}
|
|
1026
980
|
if include_post:
|
|
1027
|
-
path_ops["post"] = _post_operation(get_op, sub_qt, coll
|
|
981
|
+
path_ops["post"] = _post_operation(get_op, sub_qt, coll, cov_resp)
|
|
1028
982
|
paths[f"{base}/instances/{{instanceId}}/{sub_qt}"] = path_ops
|
|
1029
983
|
|
|
1030
984
|
elif qt == "items":
|
|
@@ -1039,7 +993,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1039
993
|
}
|
|
1040
994
|
path_ops = {"get": get_op}
|
|
1041
995
|
if include_post:
|
|
1042
|
-
path_ops["post"] = _post_operation(get_op, "items", coll
|
|
996
|
+
path_ops["post"] = _post_operation(get_op, "items", coll, cov_resp)
|
|
1043
997
|
paths[f"{base}/items"] = path_ops
|
|
1044
998
|
|
|
1045
999
|
elif qt == "locations":
|
|
@@ -1058,7 +1012,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1058
1012
|
}
|
|
1059
1013
|
path_ops = {"get": get_locations_op}
|
|
1060
1014
|
if include_post:
|
|
1061
|
-
path_ops["post"] = _post_operation(get_locations_op, "locations", coll
|
|
1015
|
+
path_ops["post"] = _post_operation(get_locations_op, "locations", coll, locations_responses)
|
|
1062
1016
|
paths[f"{base}/locations"] = path_ops
|
|
1063
1017
|
|
|
1064
1018
|
get_location_op = {
|
|
@@ -1074,7 +1028,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1074
1028
|
}
|
|
1075
1029
|
loc_path_ops = {"get": get_location_op}
|
|
1076
1030
|
if include_post:
|
|
1077
|
-
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)
|
|
1078
1032
|
paths[f"{base}/locations/{{locId}}"] = loc_path_ops
|
|
1079
1033
|
|
|
1080
1034
|
else:
|
|
@@ -1088,7 +1042,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1088
1042
|
}
|
|
1089
1043
|
path_ops = {"get": get_op}
|
|
1090
1044
|
if include_post:
|
|
1091
|
-
path_ops["post"] = _post_operation(get_op, qt, coll
|
|
1045
|
+
path_ops["post"] = _post_operation(get_op, qt, coll, cov_resp)
|
|
1092
1046
|
paths[f"{base}/{qt}"] = path_ops
|
|
1093
1047
|
|
|
1094
1048
|
return paths
|
|
@@ -1096,15 +1050,10 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1096
1050
|
|
|
1097
1051
|
def _core_paths(profile: ServiceProfile) -> dict:
|
|
1098
1052
|
version = profile.openapi_version
|
|
1099
|
-
landing_response = _landing_page_schema(
|
|
1100
|
-
profile.req_uri, version,
|
|
1101
|
-
title=profile.title,
|
|
1102
|
-
description=profile.description or "",
|
|
1103
|
-
keywords=profile.keywords or None,
|
|
1104
|
-
)
|
|
1053
|
+
landing_response = _landing_page_schema(profile)
|
|
1105
1054
|
|
|
1106
1055
|
# Conformance response with required conformance classes
|
|
1107
|
-
conformance_response =
|
|
1056
|
+
conformance_response = copy.deepcopy(_R200_CONFORMANCE)
|
|
1108
1057
|
if profile.required_conformance_classes:
|
|
1109
1058
|
if version == "3.1.0":
|
|
1110
1059
|
conformance_response["content"]["application/json"]["schema"]["properties"]["conformsTo"]["contains"] = {
|
|
@@ -1141,7 +1090,7 @@ def _core_paths(profile: ServiceProfile) -> dict:
|
|
|
1141
1090
|
"parameters": [_F, _LANG],
|
|
1142
1091
|
"responses": {"200": {"description": "Collections list", "content": {"application/json": {"schema": {"type": "object", "properties": {"links": _LINKS_ARRAY}}}}}, "400": _ERR_400, "500": _ERR_500},
|
|
1143
1092
|
}},
|
|
1144
|
-
"/
|
|
1093
|
+
"/api": {"get": {
|
|
1145
1094
|
"summary": "OpenAPI definition",
|
|
1146
1095
|
"description": "Retrieve the OpenAPI definition document for this API.",
|
|
1147
1096
|
"operationId": "getOpenAPI",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.3 → 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.3 → oapi_profile_builder-3.7.0}/src/oapi_profile_builder/compile.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.3 → 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
|