oapi-profile-builder 3.6.3__tar.gz → 3.7.1__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.1}/PKG-INFO +1 -1
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/pyproject.toml +1 -1
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/generate.py +291 -341
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1/src/oapi_profile_builder.egg-info}/PKG-INFO +1 -1
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/LICENSE +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/README.md +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/setup.cfg +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/__init__.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/cite.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/cite_features.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/cli.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/compile.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/cover.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/models.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/server_validation.py +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
- {oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/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.1"
|
|
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.1}/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,23 @@ _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)"},
|
|
412
|
-
"f": {"type": "string", "description": "Response format"},
|
|
413
|
-
},
|
|
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"},
|
|
427
|
+
"crs": {"type": "string", "description": "Identifier for the coordinate reference system"},
|
|
426
428
|
"f": {"type": "string", "description": "Response format"},
|
|
427
429
|
},
|
|
428
430
|
},
|
|
429
431
|
}
|
|
430
432
|
|
|
431
|
-
|
|
432
|
-
def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict) -> dict:
|
|
433
|
+
def _post_operation(get_op: dict, query_type: str, coll: object, responses: dict) -> dict:
|
|
433
434
|
"""Build a POST operation mirroring a GET EDR data query operation."""
|
|
434
435
|
body_schema = _POST_BODY_SCHEMAS.get(query_type, {"type": "object"})
|
|
436
|
+
import copy
|
|
437
|
+
body_schema = copy.deepcopy(body_schema)
|
|
438
|
+
|
|
439
|
+
# Filter out 'z' if collection has no vertical extent
|
|
440
|
+
if hasattr(coll, 'extent') and coll.extent and not coll.extent.vertical:
|
|
441
|
+
if "properties" in body_schema and "z" in body_schema["properties"]:
|
|
442
|
+
del body_schema["properties"]["z"]
|
|
443
|
+
|
|
435
444
|
# Carry over a default output format from the GET operation's `f` parameter
|
|
436
445
|
# so the POST body documents the same default.
|
|
437
446
|
default_f = None
|
|
@@ -440,8 +449,6 @@ def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict
|
|
|
440
449
|
default_f = p.get("schema", {}).get("default")
|
|
441
450
|
break
|
|
442
451
|
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
452
|
body_schema["properties"]["f"]["default"] = default_f
|
|
446
453
|
return {
|
|
447
454
|
"summary": get_op["summary"].replace("query", "query (POST)"),
|
|
@@ -465,35 +472,35 @@ def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict
|
|
|
465
472
|
_QUERY_PARAMS: dict[str, list[dict]] = {
|
|
466
473
|
"position": [
|
|
467
474
|
{"$ref": f"{_EDR}/parameters/positionCoords.yaml"},
|
|
468
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
475
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
469
476
|
],
|
|
470
477
|
"area": [
|
|
471
478
|
{"$ref": f"{_EDR}/parameters/areaCoords.yaml"},
|
|
472
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
479
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
473
480
|
],
|
|
474
481
|
"radius": [
|
|
475
482
|
{"$ref": f"{_EDR}/parameters/positionCoords.yaml"},
|
|
476
483
|
{"$ref": f"{_EDR}/parameters/within.yaml"},
|
|
477
484
|
{"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,
|
|
485
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
479
486
|
],
|
|
480
487
|
"cube": [
|
|
481
488
|
{"$ref": f"{_EDR}/parameters/bbox.yaml"},
|
|
482
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
489
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
483
490
|
],
|
|
484
491
|
"trajectory": [
|
|
485
492
|
{"name": "coords", "in": "query", "required": True, "description": "WKT LineString geometry", "schema": {"type": "string"}},
|
|
486
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
493
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
487
494
|
],
|
|
488
495
|
"corridor": [
|
|
489
496
|
{"name": "coords", "in": "query", "required": True, "description": "WKT LineString geometry", "schema": {"type": "string"}},
|
|
490
497
|
{"name": "corridor-width", "in": "query", "required": True, "description": "Width of the corridor", "schema": {"type": "number"}},
|
|
491
498
|
{"name": "corridor-height", "in": "query", "description": "Height of the corridor", "schema": {"type": "number"}},
|
|
492
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
499
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
493
500
|
],
|
|
494
501
|
"items": [
|
|
495
502
|
{"name": "bbox", "in": "query", "description": "Bounding box filter", "schema": {"type": "string"}},
|
|
496
|
-
_DATETIME, _PARAM_NAME, _Z, _F,
|
|
503
|
+
_DATETIME, _PARAM_NAME, _CRS, _Z, _F,
|
|
497
504
|
],
|
|
498
505
|
"locations": [
|
|
499
506
|
{"$ref": f"{_EDR}/parameters/bbox.yaml"},
|
|
@@ -502,257 +509,14 @@ _QUERY_PARAMS: dict[str, list[dict]] = {
|
|
|
502
509
|
"instances": [_F],
|
|
503
510
|
}
|
|
504
511
|
|
|
505
|
-
|
|
506
512
|
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
513
|
import re as _re
|
|
514
|
-
|
|
515
514
|
def sanitize(s: str) -> str:
|
|
516
515
|
words = _re.split(r"[^a-zA-Z0-9]+", s)
|
|
517
516
|
return "".join((w[0].upper() + w[1:]) for w in words if w)
|
|
518
|
-
|
|
519
517
|
return "".join(sanitize(p) for p in parts)
|
|
520
518
|
|
|
521
519
|
|
|
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
520
|
def _parameter_schema(version: str) -> dict:
|
|
757
521
|
"""OpenAPI schema for a single EDR Parameter object."""
|
|
758
522
|
return {
|
|
@@ -807,6 +571,163 @@ def _parameter_schema(version: str) -> dict:
|
|
|
807
571
|
}
|
|
808
572
|
|
|
809
573
|
|
|
574
|
+
def _parameter_instance_schema(param: object, version: str) -> dict:
|
|
575
|
+
"""Build a fixed OpenAPI schema for a specific Parameter instance."""
|
|
576
|
+
schema = {
|
|
577
|
+
"type": "object",
|
|
578
|
+
"required": ["type", "observedProperty"],
|
|
579
|
+
"properties": {
|
|
580
|
+
"type": {"type": "string", **_const("Parameter", version)},
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
for field in ("id", "label", "description", "data-type"):
|
|
584
|
+
val = getattr(param, field.replace("-", "_"), None)
|
|
585
|
+
if val:
|
|
586
|
+
schema["properties"][field] = {"type": "string", **_const(val, version)}
|
|
587
|
+
|
|
588
|
+
op = getattr(param, "observedProperty", None)
|
|
589
|
+
if op:
|
|
590
|
+
op_props = {"label": {"type": "string", **_const(op.label, version)}}
|
|
591
|
+
if getattr(op, "id", None):
|
|
592
|
+
op_props["id"] = {"type": "string", **_const(op.id, version)}
|
|
593
|
+
if getattr(op, "description", None):
|
|
594
|
+
op_props["description"] = {"type": "string", **_const(op.description, version)}
|
|
595
|
+
schema["properties"]["observedProperty"] = {"type": "object", "properties": op_props, "required": ["label"]}
|
|
596
|
+
|
|
597
|
+
u = getattr(param, "unit", None)
|
|
598
|
+
if u:
|
|
599
|
+
u_props = {}
|
|
600
|
+
if getattr(u, "label", None):
|
|
601
|
+
u_props["label"] = {"type": "string", **_const(u.label, version)}
|
|
602
|
+
symbol = getattr(u, "symbol", None)
|
|
603
|
+
if symbol:
|
|
604
|
+
if isinstance(symbol, str):
|
|
605
|
+
u_props["symbol"] = {"type": "string", **_const(symbol, version)}
|
|
606
|
+
elif hasattr(symbol, "value") and hasattr(symbol, "type"):
|
|
607
|
+
u_props["symbol"] = {
|
|
608
|
+
"type": "object",
|
|
609
|
+
"required": ["value", "type"],
|
|
610
|
+
"properties": {
|
|
611
|
+
"value": {"type": "string", **_const(symbol.value, version)},
|
|
612
|
+
"type": {"type": "string", **_const(symbol.type, version)}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
schema["properties"]["unit"] = {"type": "object", "properties": u_props}
|
|
616
|
+
|
|
617
|
+
return schema
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def _collection_response_schema(coll: Collection,
|
|
621
|
+
profile: "ServiceProfile | None") -> dict:
|
|
622
|
+
"""Build a 200 response schema for a single collection endpoint."""
|
|
623
|
+
version = profile.openapi_version if profile else "3.0.0"
|
|
624
|
+
crs_schema: dict = {"type": "string"}
|
|
625
|
+
trs_schema: dict = {"type": "string"}
|
|
626
|
+
vrs_schema: dict = {"type": "string"}
|
|
627
|
+
supported_crs_schema: dict = {"type": "string"}
|
|
628
|
+
|
|
629
|
+
if profile and profile.extent_requirements:
|
|
630
|
+
er = profile.extent_requirements
|
|
631
|
+
def _apply(schema: dict, constraint: "object | None") -> None:
|
|
632
|
+
if constraint is None: return
|
|
633
|
+
if getattr(constraint, "allowed", None): schema["enum"] = constraint.allowed
|
|
634
|
+
elif getattr(constraint, "pattern", None): schema["pattern"] = constraint.pattern
|
|
635
|
+
_apply(crs_schema, er.extent_crs)
|
|
636
|
+
_apply(trs_schema, er.extent_trs)
|
|
637
|
+
_apply(vrs_schema, er.extent_vrs)
|
|
638
|
+
_apply(supported_crs_schema, er.supported_crs)
|
|
639
|
+
|
|
640
|
+
coll_param_schema = getattr(coll, "parameter_schema", None)
|
|
641
|
+
if coll_param_schema:
|
|
642
|
+
import copy
|
|
643
|
+
param_item_schema = copy.deepcopy(coll_param_schema)
|
|
644
|
+
elif profile and profile.parameter_schema:
|
|
645
|
+
import copy
|
|
646
|
+
param_item_schema = copy.deepcopy(profile.parameter_schema)
|
|
647
|
+
else:
|
|
648
|
+
param_item_schema = _parameter_schema(profile.openapi_version)
|
|
649
|
+
|
|
650
|
+
param_names_schema: dict = {"type": "object", "additionalProperties": param_item_schema}
|
|
651
|
+
if coll.parameter_names:
|
|
652
|
+
param_names_schema["properties"] = {
|
|
653
|
+
name: _parameter_instance_schema(param, version)
|
|
654
|
+
for name, param in coll.parameter_names.root.items()
|
|
655
|
+
}
|
|
656
|
+
if version == "3.1.0" and profile and profile.parameter_name_pattern:
|
|
657
|
+
param_names_schema["propertyNames"] = {"type": "string", "pattern": profile.parameter_name_pattern}
|
|
658
|
+
|
|
659
|
+
output_formats_schema: dict = {"type": "array", "items": {"type": "string"}}
|
|
660
|
+
if coll.output_formats:
|
|
661
|
+
output_formats_schema["items"] = {"type": "string", "enum": list(coll.output_formats)}
|
|
662
|
+
|
|
663
|
+
if coll.crs: crs_array_schema: dict = {"type": "array", "items": {"type": "string", "enum": list(coll.crs)}}
|
|
664
|
+
else: crs_array_schema = {"type": "array", "items": supported_crs_schema}
|
|
665
|
+
|
|
666
|
+
extent_props: dict = {}
|
|
667
|
+
if coll.extent and coll.extent.spatial:
|
|
668
|
+
s = coll.extent.spatial
|
|
669
|
+
extent_props["spatial"] = {
|
|
670
|
+
"type": "object", "required": ["bbox", "crs"],
|
|
671
|
+
"properties": {
|
|
672
|
+
"bbox": {"type": "array", "items": {"type": "array", "items": {"type": "number"}}, **_const(s.bbox, version)},
|
|
673
|
+
"crs": {"type": "string", **_const(s.crs, version)}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if coll.extent and coll.extent.temporal:
|
|
677
|
+
t = coll.extent.temporal
|
|
678
|
+
temporal_props = {"trs": {"type": "string", **_const(t.trs, version)}}
|
|
679
|
+
if t.interval: temporal_props["interval"] = {"type": "array", **_const(t.interval, version)}
|
|
680
|
+
if t.values: temporal_props["values"] = {"type": "array", **_const(t.values, version)}
|
|
681
|
+
extent_props["temporal"] = {"type": "object", "required": ["trs"], "properties": temporal_props}
|
|
682
|
+
if coll.extent and coll.extent.vertical:
|
|
683
|
+
v = coll.extent.vertical
|
|
684
|
+
vertical_props = {"vrs": {"type": "string", **_const(v.vrs, version)}}
|
|
685
|
+
if v.interval: vertical_props["interval"] = {"type": "array", **_const(v.interval, version)}
|
|
686
|
+
if v.values: vertical_props["values"] = {"type": "array", **_const(v.values, version)}
|
|
687
|
+
if getattr(v, "positive", None): vertical_props["positive"] = {"type": "string", **_const(v.positive, version)}
|
|
688
|
+
extent_props["vertical"] = {"type": "object", "required": ["vrs"], "properties": vertical_props}
|
|
689
|
+
if coll.extent and coll.extent.custom:
|
|
690
|
+
custom_items = [{"type": "object", "required": ["id", "interval", "reference"],
|
|
691
|
+
"properties": {"id": {"type": "string", **_const(c.id, version)},
|
|
692
|
+
"interval": {"type": "array", **_const(c.interval, version)},
|
|
693
|
+
"reference": {"type": "string", **_const(c.reference, version)}}} for c in coll.extent.custom]
|
|
694
|
+
extent_props["custom"] = {"type": "array", "items": {"oneOf": custom_items}}
|
|
695
|
+
|
|
696
|
+
dq_props = {}
|
|
697
|
+
if coll.data_queries:
|
|
698
|
+
for dq_name, dq_val in coll.data_queries:
|
|
699
|
+
if dq_val is None: continue
|
|
700
|
+
vars_props = {}
|
|
701
|
+
vars_obj = dq_val.link.variables
|
|
702
|
+
if vars_obj:
|
|
703
|
+
for f in ("query_type", "default_output_format"):
|
|
704
|
+
v = getattr(vars_obj, f, None);
|
|
705
|
+
if v: vars_props[f] = {"type": "string", **_const(v, version)}
|
|
706
|
+
if getattr(vars_obj, "output_formats", None):
|
|
707
|
+
vars_props["output_formats"] = {"type": "array", "items": {"type": "string", "enum": list(vars_obj.output_formats)}}
|
|
708
|
+
extra = getattr(vars_obj, "model_extra", {}) or {}
|
|
709
|
+
if "crs" in extra: vars_props["crs"] = {"type": "array", "items": {"type": "string"}, **_const(extra["crs"], version)}
|
|
710
|
+
if "crs_details" in extra: vars_props["crs_details"] = {"type": "array", **_const(extra["crs_details"], version)}
|
|
711
|
+
dq_props[dq_name] = {"type": "object", "properties": {"link": {"type": "object", "properties": {
|
|
712
|
+
"href": {"type": "string", **_const(dq_val.link.href, version)},
|
|
713
|
+
"rel": {"type": "string", **_const(dq_val.link.rel, version)},
|
|
714
|
+
"variables": {"type": "object", "properties": vars_props}
|
|
715
|
+
}}}}
|
|
716
|
+
|
|
717
|
+
schema: dict = {
|
|
718
|
+
"type": "object", "required": ["id", "links", "extent"],
|
|
719
|
+
"properties": {
|
|
720
|
+
"id": {"type": "string", **_const(coll.id, version)},
|
|
721
|
+
"title": {"type": "string", **_const(coll.title, version)} if coll.title else {"type": "string"},
|
|
722
|
+
"description": {"type": "string", **_const(coll.description, version)} if coll.description else {"type": "string"},
|
|
723
|
+
"keywords": {"type": "array", "items": {"type": "string", "enum": coll.keywords}} if coll.keywords else {"type": "array"},
|
|
724
|
+
"links": _LINKS_ARRAY, "crs": crs_array_schema, "output_formats": output_formats_schema,
|
|
725
|
+
"parameter_names": param_names_schema,
|
|
726
|
+
"extent": {"type": "object", "required": ["spatial"], "properties": extent_props},
|
|
727
|
+
"data_queries": {"type": "object", "properties": dq_props}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return {"description": "Collection metadata", "content": {"application/json": {"schema": schema}}}
|
|
810
731
|
def _f_enum(profile: "ServiceProfile | None") -> list[str]:
|
|
811
732
|
"""Allowed values for the `f` (output format) parameter: media types."""
|
|
812
733
|
enum = ["json", "html"]
|
|
@@ -865,6 +786,40 @@ def _f_param(profile: "ServiceProfile | None", variables: object | None) -> dict
|
|
|
865
786
|
}
|
|
866
787
|
|
|
867
788
|
|
|
789
|
+
|
|
790
|
+
def _params_for_query(params: list[dict], profile: "ServiceProfile | None",
|
|
791
|
+
coll: Collection, variables: object | None) -> list[dict]:
|
|
792
|
+
"""Refine query parameters based on collection capabilities (z filtering, crs enum)."""
|
|
793
|
+
import copy
|
|
794
|
+
out = []
|
|
795
|
+
has_vertical = coll.extent and coll.extent.vertical
|
|
796
|
+
supported_crs = list(coll.crs) if coll.crs else []
|
|
797
|
+
|
|
798
|
+
# Check extent_requirements for supported_crs if not explicit on collection
|
|
799
|
+
if not supported_crs and profile and profile.extent_requirements and profile.extent_requirements.supported_crs:
|
|
800
|
+
supported_crs = profile.extent_requirements.supported_crs.allowed or []
|
|
801
|
+
|
|
802
|
+
for p in params:
|
|
803
|
+
if p is _Z and not has_vertical:
|
|
804
|
+
continue
|
|
805
|
+
|
|
806
|
+
# Make a copy if we need to modify (e.g. for f default or crs enum)
|
|
807
|
+
p_eff = p
|
|
808
|
+
|
|
809
|
+
# Handle f parameter default
|
|
810
|
+
if p is _F:
|
|
811
|
+
p_eff = _f_param(profile, variables)
|
|
812
|
+
|
|
813
|
+
# Handle crs parameter enum
|
|
814
|
+
elif p is _CRS and supported_crs:
|
|
815
|
+
p_eff = copy.deepcopy(p)
|
|
816
|
+
if "schema" not in p_eff: p_eff["schema"] = {}
|
|
817
|
+
p_eff["schema"]["type"] = "string"
|
|
818
|
+
p_eff["schema"]["enum"] = supported_crs
|
|
819
|
+
|
|
820
|
+
out.append(p_eff)
|
|
821
|
+
return out
|
|
822
|
+
|
|
868
823
|
def _params_with_default_f(params: list[dict], profile: "ServiceProfile | None",
|
|
869
824
|
variables: object | None) -> list[dict]:
|
|
870
825
|
"""Replace the shared `_F` reference in a params list with a query-specific default."""
|
|
@@ -979,7 +934,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
979
934
|
return qv.link.variables if qv is not None and qv.link else None
|
|
980
935
|
|
|
981
936
|
for qt in active:
|
|
982
|
-
params =
|
|
937
|
+
params = _params_for_query(_QUERY_PARAMS.get(qt, []), profile, coll, _vars(qt))
|
|
983
938
|
|
|
984
939
|
if qt == "instances":
|
|
985
940
|
paths[f"{base}/instances"] = {"get": {
|
|
@@ -1011,7 +966,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1011
966
|
}}
|
|
1012
967
|
# instance-level query sub-paths — GET + optional POST
|
|
1013
968
|
for sub_qt in (active - {"instances"}):
|
|
1014
|
-
sub_params =
|
|
969
|
+
sub_params = _params_for_query(_QUERY_PARAMS.get(sub_qt, []), profile, coll, _vars(sub_qt))
|
|
1015
970
|
if sub_qt == "items":
|
|
1016
971
|
sub_params = _with_paging_limit(sub_params, profile)
|
|
1017
972
|
get_op = {
|
|
@@ -1024,7 +979,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1024
979
|
}
|
|
1025
980
|
path_ops = {"get": get_op}
|
|
1026
981
|
if include_post:
|
|
1027
|
-
path_ops["post"] = _post_operation(get_op, sub_qt, coll
|
|
982
|
+
path_ops["post"] = _post_operation(get_op, sub_qt, coll, cov_resp)
|
|
1028
983
|
paths[f"{base}/instances/{{instanceId}}/{sub_qt}"] = path_ops
|
|
1029
984
|
|
|
1030
985
|
elif qt == "items":
|
|
@@ -1039,7 +994,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1039
994
|
}
|
|
1040
995
|
path_ops = {"get": get_op}
|
|
1041
996
|
if include_post:
|
|
1042
|
-
path_ops["post"] = _post_operation(get_op, "items", coll
|
|
997
|
+
path_ops["post"] = _post_operation(get_op, "items", coll, cov_resp)
|
|
1043
998
|
paths[f"{base}/items"] = path_ops
|
|
1044
999
|
|
|
1045
1000
|
elif qt == "locations":
|
|
@@ -1058,7 +1013,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1058
1013
|
}
|
|
1059
1014
|
path_ops = {"get": get_locations_op}
|
|
1060
1015
|
if include_post:
|
|
1061
|
-
path_ops["post"] = _post_operation(get_locations_op, "locations", coll
|
|
1016
|
+
path_ops["post"] = _post_operation(get_locations_op, "locations", coll, locations_responses)
|
|
1062
1017
|
paths[f"{base}/locations"] = path_ops
|
|
1063
1018
|
|
|
1064
1019
|
get_location_op = {
|
|
@@ -1074,7 +1029,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1074
1029
|
}
|
|
1075
1030
|
loc_path_ops = {"get": get_location_op}
|
|
1076
1031
|
if include_post:
|
|
1077
|
-
loc_path_ops["post"] = _post_operation(get_location_op, "locations", coll
|
|
1032
|
+
loc_path_ops["post"] = _post_operation(get_location_op, "locations", coll, cov_resp)
|
|
1078
1033
|
paths[f"{base}/locations/{{locId}}"] = loc_path_ops
|
|
1079
1034
|
|
|
1080
1035
|
else:
|
|
@@ -1088,7 +1043,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1088
1043
|
}
|
|
1089
1044
|
path_ops = {"get": get_op}
|
|
1090
1045
|
if include_post:
|
|
1091
|
-
path_ops["post"] = _post_operation(get_op, qt, coll
|
|
1046
|
+
path_ops["post"] = _post_operation(get_op, qt, coll, cov_resp)
|
|
1092
1047
|
paths[f"{base}/{qt}"] = path_ops
|
|
1093
1048
|
|
|
1094
1049
|
return paths
|
|
@@ -1096,15 +1051,10 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1096
1051
|
|
|
1097
1052
|
def _core_paths(profile: ServiceProfile) -> dict:
|
|
1098
1053
|
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
|
-
)
|
|
1054
|
+
landing_response = _landing_page_schema(profile)
|
|
1105
1055
|
|
|
1106
1056
|
# Conformance response with required conformance classes
|
|
1107
|
-
conformance_response =
|
|
1057
|
+
conformance_response = copy.deepcopy(_R200_CONFORMANCE)
|
|
1108
1058
|
if profile.required_conformance_classes:
|
|
1109
1059
|
if version == "3.1.0":
|
|
1110
1060
|
conformance_response["content"]["application/json"]["schema"]["properties"]["conformsTo"]["contains"] = {
|
|
@@ -1141,7 +1091,7 @@ def _core_paths(profile: ServiceProfile) -> dict:
|
|
|
1141
1091
|
"parameters": [_F, _LANG],
|
|
1142
1092
|
"responses": {"200": {"description": "Collections list", "content": {"application/json": {"schema": {"type": "object", "properties": {"links": _LINKS_ARRAY}}}}}, "400": _ERR_400, "500": _ERR_500},
|
|
1143
1093
|
}},
|
|
1144
|
-
"/
|
|
1094
|
+
"/api": {"get": {
|
|
1145
1095
|
"summary": "OpenAPI definition",
|
|
1146
1096
|
"description": "Retrieve the OpenAPI definition document for this API.",
|
|
1147
1097
|
"operationId": "getOpenAPI",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/src/oapi_profile_builder/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/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.1}/src/oapi_profile_builder/compile.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.3 → oapi_profile_builder-3.7.1}/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
|