oapi-profile-builder 3.0.0__tar.gz → 3.1.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.0.0/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.1.0}/PKG-INFO +113 -8
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/README.md +112 -7
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/pyproject.toml +1 -1
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/cli.py +1 -1
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/generate.py +231 -36
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/models.py +212 -10
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0/src/oapi_profile_builder.egg-info}/PKG-INFO +113 -8
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/LICENSE +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/setup.cfg +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/__init__.py +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/cite.py +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/cite_features.py +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/compile.py +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/server_validation.py +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
- {oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder.egg-info/top_level.txt +0 -0
{oapi_profile_builder-3.0.0/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.1.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oapi-profile-builder
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Authoritative tooling for creating OGC API Service Profiles (EDR, Features)
|
|
5
5
|
Author-email: Shane Mill <shane.mill@noaa.gov>
|
|
6
6
|
License: Apache License
|
|
@@ -290,12 +290,18 @@ abstract_tests: []
|
|
|
290
290
|
| `description` | string | no | Human-readable description of the service profile. Surfaces in the OpenAPI `info.description` and the landing page response schema |
|
|
291
291
|
| `keywords` | list | no | Service-level keywords (e.g. query types, parameter names, domain terms). Surfaces in `info.x-keywords` and the landing page response schema. Distinct from `document_metadata.keywords`, which are for the OGC PDF header |
|
|
292
292
|
| `server_url` | string | no | Documentation only — not written to the profile OpenAPI |
|
|
293
|
+
| `provider` | object | no | Service provider / responsible party. Surfaces in OpenAPI `info.contact` and the document Point of Contact (see below) |
|
|
294
|
+
| `classification` | object | no | Security classification (`level` + `system`). Surfaces as a document banner and `info.x-classification` |
|
|
295
|
+
| `metadata_date` | string | no | Metadata update date (ISO 8601). Surfaces as `info.x-metadata-date` |
|
|
296
|
+
| `resource_service_publish_date` | string | no | Resource/service publication date (ISO 8601). Surfaces as `info.x-resource-publish-date` |
|
|
297
|
+
| `resource_default_locale` | string | no | Default resource locale (e.g. `eng`). Surfaces as `info.x-default-locale` |
|
|
293
298
|
| `allow_post_queries` | bool | no | When `true`, generates `POST` alongside `GET` for all EDR data query endpoints. Defaults to `false` |
|
|
294
299
|
| `collections` | list | yes | One or more EDR collections (see below) |
|
|
295
300
|
| `required_conformance_classes` | list | no | Conformance classes implementations must declare. Defaults to EDR Core |
|
|
296
301
|
| `extent_requirements` | object | no | Profile-level CRS/TRS/VRS constraints (see below) |
|
|
297
302
|
| `output_formats` | list | no | Format name → media type + schema ref mappings |
|
|
298
303
|
| `collection_id_pattern` | string | no | Regex all collection IDs must match |
|
|
304
|
+
| `spec_uri_base` | string | no | Base URI for the requirements/conformance class identifiers. Defaults to the OGC EDR Part 3 namespace; override to publish under another SDO namespace (e.g. DGIWG) |
|
|
299
305
|
| `parameter_name_pattern` | string | no | Regex all `parameter_names` keys must match |
|
|
300
306
|
| `parameter_schema` | object | no | JSON Schema for parameter objects — replaces the default schema in the generated OpenAPI (see below) |
|
|
301
307
|
| `processes` | list | no | OGC API Processes to expose in the OpenAPI |
|
|
@@ -303,7 +309,7 @@ abstract_tests: []
|
|
|
303
309
|
| `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
|
|
304
310
|
| `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
|
|
305
311
|
| `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
|
|
306
|
-
| `document_metadata` | object | no | Metanorma PDF header
|
|
312
|
+
| `document_metadata` | object | no | Metanorma PDF header — doc number/type/subtype, status, editors, submitters, dates, notice, normative references (see below) |
|
|
307
313
|
|
|
308
314
|
---
|
|
309
315
|
|
|
@@ -360,6 +366,22 @@ data_queries:
|
|
|
360
366
|
crs_details:
|
|
361
367
|
- crs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
|
362
368
|
- crs: "http://www.opengis.net/def/crs/EPSG/0/4326"
|
|
369
|
+
cube:
|
|
370
|
+
link:
|
|
371
|
+
href: https://example.com/collections/obs/cube
|
|
372
|
+
rel: data
|
|
373
|
+
variables:
|
|
374
|
+
query_type: cube
|
|
375
|
+
output_formats: [CoverageJSON, GeoTIFF]
|
|
376
|
+
# default_output_format: response format used when the `f` query
|
|
377
|
+
# parameter is omitted. Must be one of output_formats. Sets the
|
|
378
|
+
# `default` on the generated OpenAPI `f` parameter for this query.
|
|
379
|
+
default_output_format: GeoTIFF
|
|
380
|
+
# crs: shorthand for crs_details — a plain list of CRS URIs.
|
|
381
|
+
# Validated against extent_requirements.supported_crs.
|
|
382
|
+
crs:
|
|
383
|
+
- "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
|
384
|
+
- "http://www.opengis.net/def/crs/EPSG/0/4326"
|
|
363
385
|
```
|
|
364
386
|
|
|
365
387
|
#### `parameter_names` example
|
|
@@ -536,16 +558,39 @@ pubsub:
|
|
|
536
558
|
|
|
537
559
|
### `document_metadata`
|
|
538
560
|
|
|
539
|
-
Required only when compiling a PDF with `--pdf`.
|
|
561
|
+
Required only when compiling a PDF with `--pdf`. Fields map to Metanorma OGC document attributes.
|
|
540
562
|
|
|
541
563
|
```yaml
|
|
542
564
|
document_metadata:
|
|
543
565
|
doc_number: "25-myprofile"
|
|
544
|
-
|
|
566
|
+
doc_type: draft-standard # Metanorma :doctype: (draft-standard | standard | best-practice | engineering-report | ...)
|
|
567
|
+
doc_subtype: implementation # implementation | best-practice | engineering-report | profile
|
|
568
|
+
status: approved # Metanorma :status:/:docstage: (swg-draft | public-rfc | approved | ...)
|
|
545
569
|
editors:
|
|
546
570
|
- Jane Smith
|
|
571
|
+
# submitters: structured table for page iv — each row carries its own affiliation.
|
|
572
|
+
# When provided, takes precedence over the editors/submitting_orgs fallback.
|
|
573
|
+
submitters:
|
|
574
|
+
- name: Jane Smith
|
|
575
|
+
affiliation: My Organization
|
|
576
|
+
role: editor
|
|
577
|
+
- name: Standards Working Group
|
|
578
|
+
affiliation: My SDO
|
|
547
579
|
submitting_orgs:
|
|
548
580
|
- My Organization
|
|
581
|
+
# Title-page dates (YYYY-MM-DD). Omitted dates fall back to {copyright_year}-01-01.
|
|
582
|
+
submission_date: "2026-05-01" # → :received-date:
|
|
583
|
+
approval_date: "2026-06-15" # → :issued-date:
|
|
584
|
+
publication_date: "2026-07-01" # → :published-date:
|
|
585
|
+
# Custom notice paragraph rendered in the front matter. Note: the OGC cover-page
|
|
586
|
+
# legal notice itself is generated by Metanorma from doc_type/status.
|
|
587
|
+
notice: >
|
|
588
|
+
This is a DRAFT standard and is subject to change.
|
|
589
|
+
# Additional normative references appended to the References section.
|
|
590
|
+
normative_references:
|
|
591
|
+
- anchor: DGIWG-250
|
|
592
|
+
citation: DGIWG 250
|
|
593
|
+
title: DGIWG GeoTIFF Profile
|
|
549
594
|
keywords:
|
|
550
595
|
- ogcdoc
|
|
551
596
|
- OGC API
|
|
@@ -554,6 +599,64 @@ document_metadata:
|
|
|
554
599
|
external_id: http://www.opengis.net/doc/dp/my-profile/1.0
|
|
555
600
|
```
|
|
556
601
|
|
|
602
|
+
The cover-page sub-title (e.g. "Draft Standard — Implementation — Approved") is produced by Metanorma from the `doc_type`, `doc_subtype`, and `status` combination.
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
### `provider`, `classification`, and provenance metadata
|
|
607
|
+
|
|
608
|
+
Optional service-level metadata. Each may also be set per-collection (the collection value overrides the profile root for that collection and is round-tripped into `profile_config.json`).
|
|
609
|
+
|
|
610
|
+
```yaml
|
|
611
|
+
provider:
|
|
612
|
+
name: Austrian Military
|
|
613
|
+
url: "https://www.bmlv.gv.at/english/forces/organisation.shtml"
|
|
614
|
+
contact:
|
|
615
|
+
email: presse@bmlvs.gv.at
|
|
616
|
+
phone: "+43 ..."
|
|
617
|
+
hours: Monday to Friday 08:00 - 17:00
|
|
618
|
+
instructions: During hours of Service
|
|
619
|
+
address: "Ministry of Defence, Rossauer Lände 1"
|
|
620
|
+
postalcode: A-1090
|
|
621
|
+
city: Vienna
|
|
622
|
+
country: Austria
|
|
623
|
+
|
|
624
|
+
classification:
|
|
625
|
+
level: "NATO RESTRICTED (NR)"
|
|
626
|
+
system: NATO
|
|
627
|
+
|
|
628
|
+
metadata_date: "2026-06-25T07:31Z"
|
|
629
|
+
resource_service_publish_date: "2026-06-24T14:10Z"
|
|
630
|
+
resource_default_locale: eng
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Where these surface:
|
|
634
|
+
|
|
635
|
+
- `provider` → OpenAPI `info.contact` (`name`/`url`/`email`; the remaining contact fields become `x-` extensions) and a **Point of Contact** section in the generated document.
|
|
636
|
+
- `classification` → a **security classification banner** in the document Abstract and `info.x-classification` in the OpenAPI.
|
|
637
|
+
- `metadata_date` / `resource_service_publish_date` / `resource_default_locale` → `info.x-metadata-date` / `info.x-resource-publish-date` / `info.x-default-locale`.
|
|
638
|
+
|
|
639
|
+
> The profile OpenAPI is implementation-independent (per EDR Part 3 REQ_publishing), so these describe the profile/data provenance rather than a specific deployment endpoint.
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
### `spec_uri_base`
|
|
644
|
+
|
|
645
|
+
By default, the requirements class and conformance class identifiers are published under the OGC API - EDR Part 3 namespace:
|
|
646
|
+
|
|
647
|
+
```
|
|
648
|
+
http://www.opengis.net/spec/ogcapi-edr-3/1.0/req/<name>
|
|
649
|
+
http://www.opengis.net/spec/ogcapi-edr-3/1.0/conf/<name>
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Set `spec_uri_base` to publish under a different SDO namespace. This updates the OpenAPI `x-ogc-profile` link, the requirements/conformance AsciiDoc identifiers, and the conformance section:
|
|
653
|
+
|
|
654
|
+
```yaml
|
|
655
|
+
spec_uri_base: "https://schemas.dgiwg.org/edr/1.0"
|
|
656
|
+
# → https://schemas.dgiwg.org/edr/1.0/req/<name>
|
|
657
|
+
# → https://schemas.dgiwg.org/edr/1.0/conf/<name>
|
|
658
|
+
```
|
|
659
|
+
|
|
557
660
|
---
|
|
558
661
|
|
|
559
662
|
## Validation Rules
|
|
@@ -564,10 +667,12 @@ The tool enforces these rules at build time. Violations produce clear error mess
|
|
|
564
667
|
|---|---|
|
|
565
668
|
| `name` format | Must match `^[a-z0-9_]+$` |
|
|
566
669
|
| No duplicate collection IDs | Across the whole profile |
|
|
567
|
-
| `extent_requirements` requires CRS spec |
|
|
568
|
-
| Collection CRS validated | Each `extent.spatial.crs` checked against `
|
|
569
|
-
| Collection TRS validated | Each `extent.temporal.trs` checked against `
|
|
570
|
-
| `crs_details` validated | Each `data_queries.*.variables.crs_details[].crs` checked against
|
|
670
|
+
| `extent_requirements` requires CRS spec | At least one of `extent_crs` or `supported_crs` must be set (each uses `allowed` or `pattern`) |
|
|
671
|
+
| Collection CRS validated | Each `extent.spatial.crs` checked against `extent_crs`; `crs[]` and `crs_details` checked against `supported_crs` |
|
|
672
|
+
| Collection TRS validated | Each `extent.temporal.trs` checked against `extent_trs` |
|
|
673
|
+
| `crs_details` / `crs` validated | Each `data_queries.*.variables.crs_details[].crs` and `crs[]` checked against `supported_crs` |
|
|
674
|
+
| `default_output_format` validated | Each `data_queries.*.variables.default_output_format` must be one of that query's `output_formats` |
|
|
675
|
+
| Document dates format | `submission_date` / `approval_date` / `publication_date` must be `YYYY-MM-DD` |
|
|
571
676
|
| Parameters need `unit` + `observedProperty` | Required by OGC API - EDR Part 3 |
|
|
572
677
|
| `parameter_name_pattern` enforced | All `parameter_names` keys must match if set |
|
|
573
678
|
| `collection_id_pattern` enforced | All collection IDs must match if set |
|
|
@@ -245,12 +245,18 @@ abstract_tests: []
|
|
|
245
245
|
| `description` | string | no | Human-readable description of the service profile. Surfaces in the OpenAPI `info.description` and the landing page response schema |
|
|
246
246
|
| `keywords` | list | no | Service-level keywords (e.g. query types, parameter names, domain terms). Surfaces in `info.x-keywords` and the landing page response schema. Distinct from `document_metadata.keywords`, which are for the OGC PDF header |
|
|
247
247
|
| `server_url` | string | no | Documentation only — not written to the profile OpenAPI |
|
|
248
|
+
| `provider` | object | no | Service provider / responsible party. Surfaces in OpenAPI `info.contact` and the document Point of Contact (see below) |
|
|
249
|
+
| `classification` | object | no | Security classification (`level` + `system`). Surfaces as a document banner and `info.x-classification` |
|
|
250
|
+
| `metadata_date` | string | no | Metadata update date (ISO 8601). Surfaces as `info.x-metadata-date` |
|
|
251
|
+
| `resource_service_publish_date` | string | no | Resource/service publication date (ISO 8601). Surfaces as `info.x-resource-publish-date` |
|
|
252
|
+
| `resource_default_locale` | string | no | Default resource locale (e.g. `eng`). Surfaces as `info.x-default-locale` |
|
|
248
253
|
| `allow_post_queries` | bool | no | When `true`, generates `POST` alongside `GET` for all EDR data query endpoints. Defaults to `false` |
|
|
249
254
|
| `collections` | list | yes | One or more EDR collections (see below) |
|
|
250
255
|
| `required_conformance_classes` | list | no | Conformance classes implementations must declare. Defaults to EDR Core |
|
|
251
256
|
| `extent_requirements` | object | no | Profile-level CRS/TRS/VRS constraints (see below) |
|
|
252
257
|
| `output_formats` | list | no | Format name → media type + schema ref mappings |
|
|
253
258
|
| `collection_id_pattern` | string | no | Regex all collection IDs must match |
|
|
259
|
+
| `spec_uri_base` | string | no | Base URI for the requirements/conformance class identifiers. Defaults to the OGC EDR Part 3 namespace; override to publish under another SDO namespace (e.g. DGIWG) |
|
|
254
260
|
| `parameter_name_pattern` | string | no | Regex all `parameter_names` keys must match |
|
|
255
261
|
| `parameter_schema` | object | no | JSON Schema for parameter objects — replaces the default schema in the generated OpenAPI (see below) |
|
|
256
262
|
| `processes` | list | no | OGC API Processes to expose in the OpenAPI |
|
|
@@ -258,7 +264,7 @@ abstract_tests: []
|
|
|
258
264
|
| `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
|
|
259
265
|
| `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
|
|
260
266
|
| `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
|
|
261
|
-
| `document_metadata` | object | no | Metanorma PDF header
|
|
267
|
+
| `document_metadata` | object | no | Metanorma PDF header — doc number/type/subtype, status, editors, submitters, dates, notice, normative references (see below) |
|
|
262
268
|
|
|
263
269
|
---
|
|
264
270
|
|
|
@@ -315,6 +321,22 @@ data_queries:
|
|
|
315
321
|
crs_details:
|
|
316
322
|
- crs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
|
317
323
|
- crs: "http://www.opengis.net/def/crs/EPSG/0/4326"
|
|
324
|
+
cube:
|
|
325
|
+
link:
|
|
326
|
+
href: https://example.com/collections/obs/cube
|
|
327
|
+
rel: data
|
|
328
|
+
variables:
|
|
329
|
+
query_type: cube
|
|
330
|
+
output_formats: [CoverageJSON, GeoTIFF]
|
|
331
|
+
# default_output_format: response format used when the `f` query
|
|
332
|
+
# parameter is omitted. Must be one of output_formats. Sets the
|
|
333
|
+
# `default` on the generated OpenAPI `f` parameter for this query.
|
|
334
|
+
default_output_format: GeoTIFF
|
|
335
|
+
# crs: shorthand for crs_details — a plain list of CRS URIs.
|
|
336
|
+
# Validated against extent_requirements.supported_crs.
|
|
337
|
+
crs:
|
|
338
|
+
- "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
|
339
|
+
- "http://www.opengis.net/def/crs/EPSG/0/4326"
|
|
318
340
|
```
|
|
319
341
|
|
|
320
342
|
#### `parameter_names` example
|
|
@@ -491,16 +513,39 @@ pubsub:
|
|
|
491
513
|
|
|
492
514
|
### `document_metadata`
|
|
493
515
|
|
|
494
|
-
Required only when compiling a PDF with `--pdf`.
|
|
516
|
+
Required only when compiling a PDF with `--pdf`. Fields map to Metanorma OGC document attributes.
|
|
495
517
|
|
|
496
518
|
```yaml
|
|
497
519
|
document_metadata:
|
|
498
520
|
doc_number: "25-myprofile"
|
|
499
|
-
|
|
521
|
+
doc_type: draft-standard # Metanorma :doctype: (draft-standard | standard | best-practice | engineering-report | ...)
|
|
522
|
+
doc_subtype: implementation # implementation | best-practice | engineering-report | profile
|
|
523
|
+
status: approved # Metanorma :status:/:docstage: (swg-draft | public-rfc | approved | ...)
|
|
500
524
|
editors:
|
|
501
525
|
- Jane Smith
|
|
526
|
+
# submitters: structured table for page iv — each row carries its own affiliation.
|
|
527
|
+
# When provided, takes precedence over the editors/submitting_orgs fallback.
|
|
528
|
+
submitters:
|
|
529
|
+
- name: Jane Smith
|
|
530
|
+
affiliation: My Organization
|
|
531
|
+
role: editor
|
|
532
|
+
- name: Standards Working Group
|
|
533
|
+
affiliation: My SDO
|
|
502
534
|
submitting_orgs:
|
|
503
535
|
- My Organization
|
|
536
|
+
# Title-page dates (YYYY-MM-DD). Omitted dates fall back to {copyright_year}-01-01.
|
|
537
|
+
submission_date: "2026-05-01" # → :received-date:
|
|
538
|
+
approval_date: "2026-06-15" # → :issued-date:
|
|
539
|
+
publication_date: "2026-07-01" # → :published-date:
|
|
540
|
+
# Custom notice paragraph rendered in the front matter. Note: the OGC cover-page
|
|
541
|
+
# legal notice itself is generated by Metanorma from doc_type/status.
|
|
542
|
+
notice: >
|
|
543
|
+
This is a DRAFT standard and is subject to change.
|
|
544
|
+
# Additional normative references appended to the References section.
|
|
545
|
+
normative_references:
|
|
546
|
+
- anchor: DGIWG-250
|
|
547
|
+
citation: DGIWG 250
|
|
548
|
+
title: DGIWG GeoTIFF Profile
|
|
504
549
|
keywords:
|
|
505
550
|
- ogcdoc
|
|
506
551
|
- OGC API
|
|
@@ -509,6 +554,64 @@ document_metadata:
|
|
|
509
554
|
external_id: http://www.opengis.net/doc/dp/my-profile/1.0
|
|
510
555
|
```
|
|
511
556
|
|
|
557
|
+
The cover-page sub-title (e.g. "Draft Standard — Implementation — Approved") is produced by Metanorma from the `doc_type`, `doc_subtype`, and `status` combination.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
### `provider`, `classification`, and provenance metadata
|
|
562
|
+
|
|
563
|
+
Optional service-level metadata. Each may also be set per-collection (the collection value overrides the profile root for that collection and is round-tripped into `profile_config.json`).
|
|
564
|
+
|
|
565
|
+
```yaml
|
|
566
|
+
provider:
|
|
567
|
+
name: Austrian Military
|
|
568
|
+
url: "https://www.bmlv.gv.at/english/forces/organisation.shtml"
|
|
569
|
+
contact:
|
|
570
|
+
email: presse@bmlvs.gv.at
|
|
571
|
+
phone: "+43 ..."
|
|
572
|
+
hours: Monday to Friday 08:00 - 17:00
|
|
573
|
+
instructions: During hours of Service
|
|
574
|
+
address: "Ministry of Defence, Rossauer Lände 1"
|
|
575
|
+
postalcode: A-1090
|
|
576
|
+
city: Vienna
|
|
577
|
+
country: Austria
|
|
578
|
+
|
|
579
|
+
classification:
|
|
580
|
+
level: "NATO RESTRICTED (NR)"
|
|
581
|
+
system: NATO
|
|
582
|
+
|
|
583
|
+
metadata_date: "2026-06-25T07:31Z"
|
|
584
|
+
resource_service_publish_date: "2026-06-24T14:10Z"
|
|
585
|
+
resource_default_locale: eng
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Where these surface:
|
|
589
|
+
|
|
590
|
+
- `provider` → OpenAPI `info.contact` (`name`/`url`/`email`; the remaining contact fields become `x-` extensions) and a **Point of Contact** section in the generated document.
|
|
591
|
+
- `classification` → a **security classification banner** in the document Abstract and `info.x-classification` in the OpenAPI.
|
|
592
|
+
- `metadata_date` / `resource_service_publish_date` / `resource_default_locale` → `info.x-metadata-date` / `info.x-resource-publish-date` / `info.x-default-locale`.
|
|
593
|
+
|
|
594
|
+
> The profile OpenAPI is implementation-independent (per EDR Part 3 REQ_publishing), so these describe the profile/data provenance rather than a specific deployment endpoint.
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
### `spec_uri_base`
|
|
599
|
+
|
|
600
|
+
By default, the requirements class and conformance class identifiers are published under the OGC API - EDR Part 3 namespace:
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
http://www.opengis.net/spec/ogcapi-edr-3/1.0/req/<name>
|
|
604
|
+
http://www.opengis.net/spec/ogcapi-edr-3/1.0/conf/<name>
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Set `spec_uri_base` to publish under a different SDO namespace. This updates the OpenAPI `x-ogc-profile` link, the requirements/conformance AsciiDoc identifiers, and the conformance section:
|
|
608
|
+
|
|
609
|
+
```yaml
|
|
610
|
+
spec_uri_base: "https://schemas.dgiwg.org/edr/1.0"
|
|
611
|
+
# → https://schemas.dgiwg.org/edr/1.0/req/<name>
|
|
612
|
+
# → https://schemas.dgiwg.org/edr/1.0/conf/<name>
|
|
613
|
+
```
|
|
614
|
+
|
|
512
615
|
---
|
|
513
616
|
|
|
514
617
|
## Validation Rules
|
|
@@ -519,10 +622,12 @@ The tool enforces these rules at build time. Violations produce clear error mess
|
|
|
519
622
|
|---|---|
|
|
520
623
|
| `name` format | Must match `^[a-z0-9_]+$` |
|
|
521
624
|
| No duplicate collection IDs | Across the whole profile |
|
|
522
|
-
| `extent_requirements` requires CRS spec |
|
|
523
|
-
| Collection CRS validated | Each `extent.spatial.crs` checked against `
|
|
524
|
-
| Collection TRS validated | Each `extent.temporal.trs` checked against `
|
|
525
|
-
| `crs_details` validated | Each `data_queries.*.variables.crs_details[].crs` checked against
|
|
625
|
+
| `extent_requirements` requires CRS spec | At least one of `extent_crs` or `supported_crs` must be set (each uses `allowed` or `pattern`) |
|
|
626
|
+
| Collection CRS validated | Each `extent.spatial.crs` checked against `extent_crs`; `crs[]` and `crs_details` checked against `supported_crs` |
|
|
627
|
+
| Collection TRS validated | Each `extent.temporal.trs` checked against `extent_trs` |
|
|
628
|
+
| `crs_details` / `crs` validated | Each `data_queries.*.variables.crs_details[].crs` and `crs[]` checked against `supported_crs` |
|
|
629
|
+
| `default_output_format` validated | Each `data_queries.*.variables.default_output_format` must be one of that query's `output_formats` |
|
|
630
|
+
| Document dates format | `submission_date` / `approval_date` / `publication_date` must be `YYYY-MM-DD` |
|
|
526
631
|
| Parameters need `unit` + `observedProperty` | Required by OGC API - EDR Part 3 |
|
|
527
632
|
| `parameter_name_pattern` enforced | All `parameter_names` keys must match if set |
|
|
528
633
|
| `collection_id_pattern` enforced | All collection IDs must match if set |
|
|
@@ -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.1.0"
|
|
8
8
|
description = "Authoritative tooling for creating OGC API Service Profiles (EDR, Features)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -169,7 +169,7 @@ def main() -> None:
|
|
|
169
169
|
|
|
170
170
|
def _run_validate_server(args) -> None:
|
|
171
171
|
import re
|
|
172
|
-
from
|
|
172
|
+
from oapi_profile_builder.server_validation import validate_server
|
|
173
173
|
|
|
174
174
|
if args.openapi:
|
|
175
175
|
import yaml
|
{oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/generate.py
RENAMED
|
@@ -328,6 +328,17 @@ _POST_BODY_SCHEMAS: dict[str, dict] = {
|
|
|
328
328
|
def _post_operation(get_op: dict, query_type: str, coll_id: str, responses: dict) -> dict:
|
|
329
329
|
"""Build a POST operation mirroring a GET EDR data query operation."""
|
|
330
330
|
body_schema = _POST_BODY_SCHEMAS.get(query_type, {"type": "object"})
|
|
331
|
+
# Carry over a default output format from the GET operation's `f` parameter
|
|
332
|
+
# so the POST body documents the same default.
|
|
333
|
+
default_f = None
|
|
334
|
+
for p in get_op.get("parameters", []):
|
|
335
|
+
if isinstance(p, dict) and p.get("name") == "f":
|
|
336
|
+
default_f = p.get("schema", {}).get("default")
|
|
337
|
+
break
|
|
338
|
+
if default_f and "properties" in body_schema and "f" in body_schema["properties"]:
|
|
339
|
+
import copy
|
|
340
|
+
body_schema = copy.deepcopy(body_schema)
|
|
341
|
+
body_schema["properties"]["f"]["default"] = default_f
|
|
331
342
|
return {
|
|
332
343
|
"summary": get_op["summary"].replace("query", "query (POST)"),
|
|
333
344
|
"description": get_op.get("description", ""),
|
|
@@ -583,6 +594,15 @@ def _collection_response_schema(coll: Collection,
|
|
|
583
594
|
"type": "array",
|
|
584
595
|
"items": {"type": "string"},
|
|
585
596
|
},
|
|
597
|
+
"default_output_format": {
|
|
598
|
+
"type": "string",
|
|
599
|
+
"description": "Default output format for this data query when f is omitted",
|
|
600
|
+
},
|
|
601
|
+
"crs": {
|
|
602
|
+
"type": "array",
|
|
603
|
+
"description": "CRS values supported by this data query (shorthand for crs_details)",
|
|
604
|
+
"items": {"type": "string"},
|
|
605
|
+
},
|
|
586
606
|
"crs_details": {
|
|
587
607
|
"type": "array",
|
|
588
608
|
"description": "CRS values supported by this data query",
|
|
@@ -665,12 +685,79 @@ def _parameter_schema() -> dict:
|
|
|
665
685
|
}
|
|
666
686
|
|
|
667
687
|
|
|
688
|
+
def _f_enum(profile: "ServiceProfile | None") -> list[str]:
|
|
689
|
+
"""Allowed values for the `f` (output format) parameter: media types."""
|
|
690
|
+
enum = ["json", "html"]
|
|
691
|
+
if profile:
|
|
692
|
+
for fmt in profile.output_formats:
|
|
693
|
+
if fmt.media_type not in enum:
|
|
694
|
+
enum.append(fmt.media_type)
|
|
695
|
+
return enum
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def _format_media_type(profile: "ServiceProfile | None", format_name: str) -> str:
|
|
699
|
+
"""Resolve a format name (e.g. 'GeoTIFF') to its media type, falling back to the name."""
|
|
700
|
+
if profile:
|
|
701
|
+
for fmt in profile.output_formats:
|
|
702
|
+
if fmt.name == format_name:
|
|
703
|
+
return fmt.media_type
|
|
704
|
+
return format_name
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _query_default_format(variables: object) -> str | None:
|
|
708
|
+
"""Read default_output_format from an EDR query's variables.
|
|
709
|
+
|
|
710
|
+
edr-pydantic's Variables defines default_output_format as a real field;
|
|
711
|
+
fall back to model_extra for forward compatibility.
|
|
712
|
+
"""
|
|
713
|
+
val = getattr(variables, "default_output_format", None)
|
|
714
|
+
if val is None:
|
|
715
|
+
extra = getattr(variables, "model_extra", None) or {}
|
|
716
|
+
val = extra.get("default_output_format")
|
|
717
|
+
return val
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def _f_param(profile: "ServiceProfile | None", variables: object | None) -> dict:
|
|
721
|
+
"""Return the `f` parameter for a query.
|
|
722
|
+
|
|
723
|
+
When the query's variables declare a default_output_format, an inline `f`
|
|
724
|
+
parameter is returned carrying that media type as its `default`. Otherwise
|
|
725
|
+
the shared component reference is used.
|
|
726
|
+
"""
|
|
727
|
+
default_fmt = _query_default_format(variables) if variables is not None else None
|
|
728
|
+
if not default_fmt:
|
|
729
|
+
return _F
|
|
730
|
+
return {
|
|
731
|
+
"name": "f", "in": "query", "required": False,
|
|
732
|
+
"description": (
|
|
733
|
+
"The optional f parameter indicates the output format which the server "
|
|
734
|
+
"shall provide as part of the response document. Defaults to "
|
|
735
|
+
f"{default_fmt} for this query."
|
|
736
|
+
),
|
|
737
|
+
"schema": {
|
|
738
|
+
"type": "string",
|
|
739
|
+
"enum": _f_enum(profile),
|
|
740
|
+
"default": _format_media_type(profile, default_fmt),
|
|
741
|
+
},
|
|
742
|
+
"style": "form", "explode": False,
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def _params_with_default_f(params: list[dict], profile: "ServiceProfile | None",
|
|
747
|
+
variables: object | None) -> list[dict]:
|
|
748
|
+
"""Replace the shared `_F` reference in a params list with a query-specific default."""
|
|
749
|
+
f_param = _f_param(profile, variables)
|
|
750
|
+
if f_param is _F:
|
|
751
|
+
return params
|
|
752
|
+
return [f_param if p is _F else p for p in params]
|
|
753
|
+
|
|
754
|
+
|
|
668
755
|
def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
669
756
|
profile: ServiceProfile | None = None) -> dict:
|
|
670
757
|
paths: dict = {}
|
|
671
|
-
base = f"/collections/{coll.id}"
|
|
672
758
|
tag = coll.id
|
|
673
759
|
desc = getattr(coll, "description", None) or coll.id
|
|
760
|
+
base = f"/collections/{coll.id}"
|
|
674
761
|
|
|
675
762
|
# Resolve effective POST flag: collection-level overrides profile-level default.
|
|
676
763
|
# Collection.post_queries is None → inherit; True/False → explicit override.
|
|
@@ -726,10 +813,15 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
726
813
|
if not coll.data_queries:
|
|
727
814
|
return paths
|
|
728
815
|
|
|
729
|
-
|
|
816
|
+
dq_map = {name: val for name, val in coll.data_queries if val is not None}
|
|
817
|
+
active = set(dq_map)
|
|
818
|
+
|
|
819
|
+
def _vars(query_type: str) -> object | None:
|
|
820
|
+
qv = dq_map.get(query_type)
|
|
821
|
+
return qv.link.variables if qv is not None and qv.link else None
|
|
730
822
|
|
|
731
823
|
for qt in active:
|
|
732
|
-
params = _QUERY_PARAMS.get(qt, [])
|
|
824
|
+
params = _params_with_default_f(_QUERY_PARAMS.get(qt, []), profile, _vars(qt))
|
|
733
825
|
|
|
734
826
|
if qt == "instances":
|
|
735
827
|
paths[f"{base}/instances"] = {"get": {
|
|
@@ -761,7 +853,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
761
853
|
}}
|
|
762
854
|
# instance-level query sub-paths — GET + optional POST
|
|
763
855
|
for sub_qt in (active - {"instances"}):
|
|
764
|
-
sub_params = _QUERY_PARAMS.get(sub_qt, [])
|
|
856
|
+
sub_params = _params_with_default_f(_QUERY_PARAMS.get(sub_qt, []), profile, _vars(sub_qt))
|
|
765
857
|
get_op = {
|
|
766
858
|
"summary": f"query {coll.id} instance by {sub_qt}",
|
|
767
859
|
"description": desc,
|
|
@@ -813,7 +905,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
813
905
|
"tags": [tag],
|
|
814
906
|
"parameters": [
|
|
815
907
|
{"$ref": f"{_EDR}/parameters/locationId.yaml"},
|
|
816
|
-
_DATETIME, _PARAM_NAME,
|
|
908
|
+
_DATETIME, _PARAM_NAME, _f_param(profile, _vars("locations")),
|
|
817
909
|
],
|
|
818
910
|
"responses": cov_resp,
|
|
819
911
|
}
|
|
@@ -1017,32 +1109,60 @@ def build_openapi(profile: ServiceProfile) -> dict:
|
|
|
1017
1109
|
tags += [{"name": "jobs"}]
|
|
1018
1110
|
|
|
1019
1111
|
# Build f parameter enum from profile output formats + standard formats
|
|
1020
|
-
f_enum =
|
|
1021
|
-
for fmt in profile.output_formats:
|
|
1022
|
-
if fmt.media_type not in f_enum:
|
|
1023
|
-
f_enum.append(fmt.media_type)
|
|
1112
|
+
f_enum = _f_enum(profile)
|
|
1024
1113
|
|
|
1025
|
-
# Build contact
|
|
1114
|
+
# Build the info.contact block. A profile-level `provider` takes precedence;
|
|
1115
|
+
# otherwise fall back to document_metadata editors/orgs.
|
|
1026
1116
|
m = profile.document_metadata
|
|
1027
1117
|
contact: dict = {}
|
|
1028
|
-
if
|
|
1118
|
+
if profile.provider:
|
|
1119
|
+
p = profile.provider
|
|
1120
|
+
contact["name"] = p.name
|
|
1121
|
+
if p.url:
|
|
1122
|
+
contact["url"] = p.url
|
|
1123
|
+
if p.contact:
|
|
1124
|
+
c = p.contact
|
|
1125
|
+
if c.email:
|
|
1126
|
+
contact["email"] = c.email
|
|
1127
|
+
# OpenAPI contact only models name/url/email — carry the rest as x- extensions.
|
|
1128
|
+
for key in ("phone", "hours", "instructions", "address", "postalcode", "city", "country"):
|
|
1129
|
+
val = getattr(c, key, None)
|
|
1130
|
+
if val:
|
|
1131
|
+
contact[f"x-{key}"] = val
|
|
1132
|
+
if "name" not in contact and m and m.editors:
|
|
1029
1133
|
contact["name"] = m.editors[0]
|
|
1030
|
-
if m and m.submitting_orgs:
|
|
1134
|
+
if "x-organization" not in contact and m and m.submitting_orgs:
|
|
1031
1135
|
contact["x-organization"] = m.submitting_orgs[0]
|
|
1032
1136
|
# Fall back to a generic contact if nothing is specified
|
|
1033
1137
|
if not contact:
|
|
1034
1138
|
contact["name"] = profile.title
|
|
1035
1139
|
|
|
1140
|
+
# Assemble the info block, adding optional x- metadata extensions.
|
|
1141
|
+
info: dict = {
|
|
1142
|
+
"title": profile.title,
|
|
1143
|
+
"version": profile.version,
|
|
1144
|
+
"description": profile.description or f"OGC API - EDR Part 3 Service Profile: {profile.title}",
|
|
1145
|
+
"x-ogc-profile": profile.req_uri,
|
|
1146
|
+
"x-keywords": profile.keywords if profile.keywords else [],
|
|
1147
|
+
"contact": contact,
|
|
1148
|
+
}
|
|
1149
|
+
if profile.classification:
|
|
1150
|
+
info["x-classification"] = {
|
|
1151
|
+
k: v for k, v in (
|
|
1152
|
+
("level", profile.classification.level),
|
|
1153
|
+
("system", profile.classification.system),
|
|
1154
|
+
) if v
|
|
1155
|
+
}
|
|
1156
|
+
if profile.metadata_date:
|
|
1157
|
+
info["x-metadata-date"] = profile.metadata_date
|
|
1158
|
+
if profile.resource_service_publish_date:
|
|
1159
|
+
info["x-resource-publish-date"] = profile.resource_service_publish_date
|
|
1160
|
+
if profile.resource_default_locale:
|
|
1161
|
+
info["x-default-locale"] = profile.resource_default_locale
|
|
1162
|
+
|
|
1036
1163
|
return {
|
|
1037
1164
|
"openapi": "3.1.0",
|
|
1038
|
-
"info":
|
|
1039
|
-
"title": profile.title,
|
|
1040
|
-
"version": profile.version,
|
|
1041
|
-
"description": profile.description or f"OGC API - EDR Part 3 Service Profile: {profile.title}",
|
|
1042
|
-
"x-ogc-profile": profile.req_uri,
|
|
1043
|
-
"x-keywords": profile.keywords if profile.keywords else [],
|
|
1044
|
-
"contact": contact,
|
|
1045
|
-
},
|
|
1165
|
+
"info": info,
|
|
1046
1166
|
# Per OGC API - EDR Part 3 REQ_publishing: this profile is implementation-independent.
|
|
1047
1167
|
# The placeholder server URL "/" is required by OpenAPI validators but implementations
|
|
1048
1168
|
# SHALL substitute their own server URL when deploying this profile.
|
|
@@ -1248,10 +1368,12 @@ def _individual_test_adoc(profile: ServiceProfile, test_id: str) -> str:
|
|
|
1248
1368
|
def _build_document_adoc(profile: ServiceProfile) -> str:
|
|
1249
1369
|
m = profile.document_metadata
|
|
1250
1370
|
year = m.copyright_year if m else 2026
|
|
1251
|
-
|
|
1252
|
-
doctype
|
|
1253
|
-
|
|
1254
|
-
docsubtype
|
|
1371
|
+
default_date = f"{year}-01-01"
|
|
1372
|
+
# doctype: configurable; draft-standard is the default for in-progress profiles
|
|
1373
|
+
doctype = (m.doc_type if m and m.doc_type else "draft-standard")
|
|
1374
|
+
# docsubtype: 'profile' is the natural subtype for a service profile, but when
|
|
1375
|
+
# doctype is draft-standard/standard OGC expects implementation/profile etc.
|
|
1376
|
+
docsubtype = m.doc_subtype if m else "implementation"
|
|
1255
1377
|
lines = [
|
|
1256
1378
|
f"= {profile.title}",
|
|
1257
1379
|
f":doctype: {doctype}",
|
|
@@ -1261,10 +1383,12 @@ def _build_document_adoc(profile: ServiceProfile) -> str:
|
|
|
1261
1383
|
":committee: technical",
|
|
1262
1384
|
f":docnumber: {m.doc_number if m else profile.name}",
|
|
1263
1385
|
f":copyright-year: {year}",
|
|
1264
|
-
f":published-date: {
|
|
1265
|
-
f":issued-date: {
|
|
1266
|
-
f":received-date: {
|
|
1386
|
+
f":published-date: {(m.publication_date if m and m.publication_date else default_date)}",
|
|
1387
|
+
f":issued-date: {(m.approval_date if m and m.approval_date else default_date)}",
|
|
1388
|
+
f":received-date: {(m.submission_date if m and m.submission_date else default_date)}",
|
|
1267
1389
|
]
|
|
1390
|
+
if m and m.status:
|
|
1391
|
+
lines.append(f":status: {m.status}")
|
|
1268
1392
|
if m and m.external_id:
|
|
1269
1393
|
lines.append(f":external-id: {m.external_id}")
|
|
1270
1394
|
if m and m.editors:
|
|
@@ -1303,18 +1427,91 @@ def _build_document_adoc(profile: ServiceProfile) -> str:
|
|
|
1303
1427
|
def _build_sections(profile: ServiceProfile) -> dict[str, str]:
|
|
1304
1428
|
"""Return the minimal boilerplate sections required by Metanorma OGC."""
|
|
1305
1429
|
m = profile.document_metadata
|
|
1306
|
-
conf_uris = [
|
|
1307
|
-
submitters = m.submitting_orgs if m and m.submitting_orgs else ["Unknown"]
|
|
1430
|
+
conf_uris = [profile.conf_uri]
|
|
1308
1431
|
req_includes = "\n".join(
|
|
1309
1432
|
f"include::../requirements/core/REQ_{r.id}.adoc[]" for r in profile.requirements
|
|
1310
1433
|
)
|
|
1311
1434
|
ats_includes = "\n".join(
|
|
1312
1435
|
f"include::../abstract_tests/core/ATS_{t.id}.adoc[]" for t in profile.abstract_tests
|
|
1313
1436
|
)
|
|
1437
|
+
|
|
1438
|
+
# --- Submitters table rows (page iv) ---
|
|
1439
|
+
# Prefer the structured `submitters` list (each with its own affiliation).
|
|
1440
|
+
# Fall back to pairing editors with the first submitting org.
|
|
1441
|
+
if m and m.submitters:
|
|
1442
|
+
submitter_rows = "\n".join(
|
|
1443
|
+
f"| {s.name}{f' _({s.role})_' if s.role else ''} |{s.affiliation or ''}"
|
|
1444
|
+
for s in m.submitters
|
|
1445
|
+
)
|
|
1446
|
+
else:
|
|
1447
|
+
fallback_org = (m.submitting_orgs[0] if m and m.submitting_orgs else "")
|
|
1448
|
+
submitter_rows = "\n".join(
|
|
1449
|
+
f"| {editor} _(editor)_ |{fallback_org}"
|
|
1450
|
+
for editor in (m.editors if m and m.editors else ["Unknown"])
|
|
1451
|
+
)
|
|
1452
|
+
|
|
1453
|
+
# --- Notice paragraph (front matter) ---
|
|
1454
|
+
notice_block = ""
|
|
1455
|
+
if m and m.notice:
|
|
1456
|
+
notice_block = (
|
|
1457
|
+
"\n[NOTE]\n"
|
|
1458
|
+
"====\n"
|
|
1459
|
+
f"{m.notice}\n"
|
|
1460
|
+
"====\n"
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
# --- References: base OGC refs + any profile-supplied normative references ---
|
|
1464
|
+
reference_lines = [
|
|
1465
|
+
"* [[[OGC-EDR-1,OGC 19-086r6]]], OGC API - Environmental Data Retrieval Standard",
|
|
1466
|
+
"* [[[OGC-EDR-3,nofetch(OGC ogcapi-edr-3)]]], OGC API - EDR Part 3: Service Profiles (draft)",
|
|
1467
|
+
]
|
|
1468
|
+
if m and m.normative_references:
|
|
1469
|
+
for ref in m.normative_references:
|
|
1470
|
+
reference_lines.append(f"* [[[{ref.anchor},{ref.citation}]]], {ref.title}")
|
|
1471
|
+
|
|
1472
|
+
# --- Classification banner (e.g. NATO RESTRICTED) ---
|
|
1473
|
+
classification_banner = ""
|
|
1474
|
+
if profile.classification:
|
|
1475
|
+
cl = profile.classification
|
|
1476
|
+
label = cl.level + (f" ({cl.system})" if cl.system else "")
|
|
1477
|
+
classification_banner = f"*SECURITY CLASSIFICATION: {label}*\n\n"
|
|
1478
|
+
|
|
1479
|
+
# --- Point of contact paragraph (from provider) ---
|
|
1480
|
+
contact_block = ""
|
|
1481
|
+
if profile.provider:
|
|
1482
|
+
pv = profile.provider
|
|
1483
|
+
parts_ = [f"This service is provided by {pv.name}"]
|
|
1484
|
+
if pv.url:
|
|
1485
|
+
parts_.append(f" ({pv.url})")
|
|
1486
|
+
parts_.append(".")
|
|
1487
|
+
if pv.contact:
|
|
1488
|
+
c = pv.contact
|
|
1489
|
+
details = []
|
|
1490
|
+
if c.email:
|
|
1491
|
+
details.append(f"Email: {c.email}")
|
|
1492
|
+
if c.phone:
|
|
1493
|
+
details.append(f"Phone: {c.phone}")
|
|
1494
|
+
addr = ", ".join(
|
|
1495
|
+
x for x in (c.address, c.postalcode, c.city, c.country) if x
|
|
1496
|
+
)
|
|
1497
|
+
if addr:
|
|
1498
|
+
details.append(f"Address: {addr}")
|
|
1499
|
+
if c.hours:
|
|
1500
|
+
details.append(f"Hours: {c.hours}")
|
|
1501
|
+
if c.instructions:
|
|
1502
|
+
details.append(f"Contact instructions: {c.instructions}")
|
|
1503
|
+
if details:
|
|
1504
|
+
parts_.append(" " + " +\n".join(details))
|
|
1505
|
+
contact_block = (
|
|
1506
|
+
"\n[.preface]\n== Point of Contact\n\n"
|
|
1507
|
+
+ "".join(parts_) + "\n"
|
|
1508
|
+
)
|
|
1509
|
+
|
|
1314
1510
|
return {
|
|
1315
1511
|
"sections/00-abstract.adoc": (
|
|
1316
1512
|
"[abstract]\n== Abstract\n\n"
|
|
1317
|
-
|
|
1513
|
+
+ classification_banner
|
|
1514
|
+
+ f"This document defines the {profile.title}, "
|
|
1318
1515
|
"an OGC API - Environmental Data Retrieval (EDR) Part 3 Service Profile. "
|
|
1319
1516
|
"It specifies normative requirements and conformance tests for server implementations "
|
|
1320
1517
|
f"conforming to this profile.\n"
|
|
@@ -1325,15 +1522,14 @@ def _build_sections(profile: ServiceProfile) -> dict[str, str]:
|
|
|
1325
1522
|
".Submitters\n"
|
|
1326
1523
|
"|===\n"
|
|
1327
1524
|
"h|Name h|Affiliation\n\n"
|
|
1328
|
-
+
|
|
1329
|
-
f"| {editor} _(editor)_ |{submitters[0] if submitters else ''}"
|
|
1330
|
-
for editor in (m.editors if m and m.editors else ["Unknown"])
|
|
1331
|
-
)
|
|
1525
|
+
+ submitter_rows
|
|
1332
1526
|
+ "\n|===\n"
|
|
1333
1527
|
),
|
|
1334
1528
|
"sections/01-preface.adoc": (
|
|
1335
1529
|
"[.preface]\n== Preface\n\n"
|
|
1336
1530
|
f"This document was prepared by {', '.join(m.submitting_orgs) if m and m.submitting_orgs else 'the submitting organizations'}.\n"
|
|
1531
|
+
+ notice_block
|
|
1532
|
+
+ contact_block
|
|
1337
1533
|
),
|
|
1338
1534
|
"sections/02-scope.adoc": (
|
|
1339
1535
|
"== Scope\n\n"
|
|
@@ -1348,8 +1544,7 @@ def _build_sections(profile: ServiceProfile) -> dict[str, str]:
|
|
|
1348
1544
|
),
|
|
1349
1545
|
"sections/04-references.adoc": (
|
|
1350
1546
|
"[bibliography]\n== References\n\n"
|
|
1351
|
-
"
|
|
1352
|
-
"* [[[OGC-EDR-3,nofetch(OGC ogcapi-edr-3)]]], OGC API - EDR Part 3: Service Profiles (draft)\n"
|
|
1547
|
+
+ "\n".join(reference_lines) + "\n"
|
|
1353
1548
|
),
|
|
1354
1549
|
"sections/05-terms.adoc": (
|
|
1355
1550
|
"== Terms, Definitions and Abbreviated Terms\n\n"
|
{oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/models.py
RENAMED
|
@@ -79,6 +79,31 @@ class ExtentWithNullBounds(EDRExtent):
|
|
|
79
79
|
temporal: Optional[TemporalWithNullBounds] = None
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
class Classification(BaseModel):
|
|
83
|
+
"""Security classification metadata (e.g. for DGIWG / military profiles)."""
|
|
84
|
+
level: str = Field(description="Classification level, e.g. 'NATO RESTRICTED (NR)'")
|
|
85
|
+
system: str | None = Field(default=None, description="Classification system, e.g. 'NATO'")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ProviderContact(BaseModel):
|
|
89
|
+
"""Point-of-contact details for the service provider (OWS/ISO-style)."""
|
|
90
|
+
email: str | None = None
|
|
91
|
+
phone: str | None = None
|
|
92
|
+
hours: str | None = None
|
|
93
|
+
instructions: str | None = None
|
|
94
|
+
address: str | None = None
|
|
95
|
+
postalcode: str | None = None
|
|
96
|
+
city: str | None = None
|
|
97
|
+
country: str | None = None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Provider(BaseModel):
|
|
101
|
+
"""Service provider / responsible party (OWS ServiceProvider-like)."""
|
|
102
|
+
name: str
|
|
103
|
+
url: str | None = None
|
|
104
|
+
contact: ProviderContact | None = None
|
|
105
|
+
|
|
106
|
+
|
|
82
107
|
class Collection(EDRCollection):
|
|
83
108
|
"""
|
|
84
109
|
EDR Collection with a null-aware temporal extent and optional parameter_names.
|
|
@@ -116,6 +141,13 @@ class Collection(EDRCollection):
|
|
|
116
141
|
"Omit (null) to inherit the profile-level default."
|
|
117
142
|
),
|
|
118
143
|
)
|
|
144
|
+
# Optional per-collection metadata. When set, overrides the profile-level
|
|
145
|
+
# value for this collection. Round-tripped into profile_config.json.
|
|
146
|
+
classification: Optional[Classification] = None
|
|
147
|
+
provider: Optional[Provider] = None
|
|
148
|
+
metadata_date: Optional[str] = None
|
|
149
|
+
resource_service_publish_date: Optional[str] = None
|
|
150
|
+
resource_default_locale: Optional[str] = None
|
|
119
151
|
|
|
120
152
|
|
|
121
153
|
# ---------------------------------------------------------------------------
|
|
@@ -255,16 +287,112 @@ class OutputFormat(BaseModel):
|
|
|
255
287
|
schema_ref: str | None = Field(default=None, description="URL to schema definition")
|
|
256
288
|
|
|
257
289
|
|
|
290
|
+
class Submitter(BaseModel):
|
|
291
|
+
"""A single entry in the document's submitters table (page iv).
|
|
292
|
+
|
|
293
|
+
Allows each submitter to carry their own affiliation, rather than sharing
|
|
294
|
+
a single organisation across every editor.
|
|
295
|
+
"""
|
|
296
|
+
name: str
|
|
297
|
+
affiliation: str | None = None
|
|
298
|
+
role: str | None = Field(
|
|
299
|
+
default=None,
|
|
300
|
+
description="Role annotation shown next to the name, e.g. 'editor' or 'contributor'",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class NormativeReference(BaseModel):
|
|
305
|
+
"""An additional normative/bibliographic reference for the References section.
|
|
306
|
+
|
|
307
|
+
Rendered as a Metanorma bibliography entry of the form
|
|
308
|
+
``* [[[<anchor>,<citation>]]], <title>``.
|
|
309
|
+
"""
|
|
310
|
+
anchor: Annotated[str, Field(pattern=r"^[A-Za-z0-9_\-]+$")] = Field(
|
|
311
|
+
description="Cross-reference anchor (letters, digits, dash, underscore), e.g. DGIWG-250",
|
|
312
|
+
)
|
|
313
|
+
citation: str = Field(description="Short citation label, e.g. 'DGIWG 250' or 'OGC 19-086r6'")
|
|
314
|
+
title: str = Field(description="Full reference title")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# Metanorma OGC document stages (`:status:` / `:docstage:`). See
|
|
318
|
+
# https://www.metanorma.org/author/ogc/ref/document-attributes/
|
|
319
|
+
DocStatus = Literal[
|
|
320
|
+
"draft",
|
|
321
|
+
"work-item-draft",
|
|
322
|
+
"swg-draft",
|
|
323
|
+
"oab-review",
|
|
324
|
+
"public-rfc",
|
|
325
|
+
"tc-vote",
|
|
326
|
+
"approved",
|
|
327
|
+
"published",
|
|
328
|
+
"deprecated",
|
|
329
|
+
"rescinded",
|
|
330
|
+
"retired",
|
|
331
|
+
"legacy",
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
# Loose ISO 8601 date (YYYY-MM-DD) check for document dates.
|
|
335
|
+
_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
336
|
+
|
|
337
|
+
|
|
258
338
|
class DocumentMetadata(BaseModel):
|
|
259
339
|
"""Metanorma/OGC document header metadata for PDF compilation."""
|
|
260
340
|
doc_number: str
|
|
261
|
-
|
|
341
|
+
doc_type: str = Field(
|
|
342
|
+
default="draft-standard",
|
|
343
|
+
description=(
|
|
344
|
+
"Metanorma OGC :doctype: value (e.g. 'draft-standard', 'standard', "
|
|
345
|
+
"'best-practice', 'engineering-report'). Drives the cover-page document type."
|
|
346
|
+
),
|
|
347
|
+
)
|
|
348
|
+
doc_subtype: Literal["implementation", "best-practice", "engineering-report", "profile"] = "implementation"
|
|
349
|
+
status: DocStatus | None = Field(
|
|
350
|
+
default=None,
|
|
351
|
+
description=(
|
|
352
|
+
"Metanorma OGC :status:/:docstage: value (e.g. 'swg-draft', 'approved'). "
|
|
353
|
+
"Combined with doc_type/doc_subtype this produces the cover-page sub-title."
|
|
354
|
+
),
|
|
355
|
+
)
|
|
262
356
|
editors: list[str] = Field(default_factory=list)
|
|
263
357
|
submitting_orgs: list[str] = Field(default_factory=list)
|
|
358
|
+
submitters: list[Submitter] = Field(
|
|
359
|
+
default_factory=list,
|
|
360
|
+
description=(
|
|
361
|
+
"Structured submitters table (name + affiliation + role) for page iv. "
|
|
362
|
+
"When provided, takes precedence over the editors/submitting_orgs fallback."
|
|
363
|
+
),
|
|
364
|
+
)
|
|
264
365
|
keywords: list[str] = Field(default_factory=list)
|
|
265
366
|
copyright_year: int = Field(default=2026)
|
|
367
|
+
# Title-page dates. Map to Metanorma attributes:
|
|
368
|
+
# submission_date -> :received-date: (:submissionDate:)
|
|
369
|
+
# approval_date -> :issued-date: (:approvalDate:)
|
|
370
|
+
# publication_date -> :published-date: (:publicationDate:)
|
|
371
|
+
# Format: YYYY-MM-DD. When omitted, falls back to {copyright_year}-01-01.
|
|
372
|
+
submission_date: str | None = Field(default=None, description="Submission date (YYYY-MM-DD)")
|
|
373
|
+
approval_date: str | None = Field(default=None, description="Approval date (YYYY-MM-DD)")
|
|
374
|
+
publication_date: str | None = Field(default=None, description="Publication date (YYYY-MM-DD)")
|
|
375
|
+
notice: str | None = Field(
|
|
376
|
+
default=None,
|
|
377
|
+
description=(
|
|
378
|
+
"Custom notice text rendered in the document front matter. Note: the OGC "
|
|
379
|
+
"cover-page legal notice itself is generated by Metanorma from doc_type/status; "
|
|
380
|
+
"this field adds a notice paragraph in the preface."
|
|
381
|
+
),
|
|
382
|
+
)
|
|
383
|
+
normative_references: list[NormativeReference] = Field(
|
|
384
|
+
default_factory=list,
|
|
385
|
+
description="Additional normative references appended to the References section.",
|
|
386
|
+
)
|
|
266
387
|
external_id: str | None = None
|
|
267
388
|
|
|
389
|
+
@field_validator("submission_date", "approval_date", "publication_date")
|
|
390
|
+
@classmethod
|
|
391
|
+
def _valid_date(cls, v: str | None) -> str | None:
|
|
392
|
+
if v is not None and not _DATE_RE.match(v):
|
|
393
|
+
raise ValueError("date must be in YYYY-MM-DD format")
|
|
394
|
+
return v
|
|
395
|
+
|
|
268
396
|
|
|
269
397
|
class PubSubServer(BaseModel):
|
|
270
398
|
"""A single pub/sub server endpoint."""
|
|
@@ -324,6 +452,34 @@ class ServiceProfile(BaseModel):
|
|
|
324
452
|
),
|
|
325
453
|
)
|
|
326
454
|
server_url: str | None = Field(default=None, description="Base URL for implementation (not used in profile OpenAPI per standard)")
|
|
455
|
+
provider: Provider | None = Field(
|
|
456
|
+
default=None,
|
|
457
|
+
description=(
|
|
458
|
+
"Service provider / responsible party. Surfaces in the OpenAPI info.contact "
|
|
459
|
+
"block (name/url/email plus x- extensions for phone, address, etc.) and as the "
|
|
460
|
+
"document point of contact."
|
|
461
|
+
),
|
|
462
|
+
)
|
|
463
|
+
classification: Classification | None = Field(
|
|
464
|
+
default=None,
|
|
465
|
+
description=(
|
|
466
|
+
"Security classification of the service/profile (e.g. NATO RESTRICTED). "
|
|
467
|
+
"Surfaces as a banner in the generated document and as info.x-classification "
|
|
468
|
+
"in the OpenAPI."
|
|
469
|
+
),
|
|
470
|
+
)
|
|
471
|
+
metadata_date: str | None = Field(
|
|
472
|
+
default=None,
|
|
473
|
+
description="Date the metadata was last updated (ISO 8601 string). Surfaces as info.x-metadata-date.",
|
|
474
|
+
)
|
|
475
|
+
resource_service_publish_date: str | None = Field(
|
|
476
|
+
default=None,
|
|
477
|
+
description="Date the resource/service was published (ISO 8601 string). Surfaces as info.x-resource-publish-date.",
|
|
478
|
+
)
|
|
479
|
+
resource_default_locale: str | None = Field(
|
|
480
|
+
default=None,
|
|
481
|
+
description="Default locale of the resource (e.g. 'eng'). Surfaces as info.x-default-locale.",
|
|
482
|
+
)
|
|
327
483
|
collections: list[Collection] = Field(min_length=1)
|
|
328
484
|
collection_examples: dict[str, dict] = Field(default_factory=dict)
|
|
329
485
|
requirements: list[Requirement] = Field(default_factory=list)
|
|
@@ -361,6 +517,16 @@ class ServiceProfile(BaseModel):
|
|
|
361
517
|
default=None,
|
|
362
518
|
description="Regex pattern for valid collection IDs"
|
|
363
519
|
)
|
|
520
|
+
spec_uri_base: str = Field(
|
|
521
|
+
default="http://www.opengis.net/spec/ogcapi-edr-3/1.0",
|
|
522
|
+
description=(
|
|
523
|
+
"Base URI for the requirements class and conformance class identifiers. "
|
|
524
|
+
"Defaults to the OGC API - EDR Part 3 namespace. Override this to publish "
|
|
525
|
+
"under a different SDO namespace (e.g. a DGIWG location). Used to derive "
|
|
526
|
+
"req_uri and conf_uri, which appear in the OpenAPI x-ogc-profile link, the "
|
|
527
|
+
"requirements/conformance AsciiDoc identifiers, and the conformance section."
|
|
528
|
+
),
|
|
529
|
+
)
|
|
364
530
|
parameter_name_pattern: str | None = Field(
|
|
365
531
|
default=None,
|
|
366
532
|
description="Regex pattern that all parameter_names keys must match"
|
|
@@ -376,14 +542,14 @@ class ServiceProfile(BaseModel):
|
|
|
376
542
|
),
|
|
377
543
|
)
|
|
378
544
|
|
|
379
|
-
# OGC identifiers derived from name — not user-supplied
|
|
545
|
+
# OGC identifiers derived from name and spec_uri_base — not user-supplied directly
|
|
380
546
|
@property
|
|
381
547
|
def req_uri(self) -> str:
|
|
382
|
-
return f"
|
|
548
|
+
return f"{self.spec_uri_base.rstrip('/')}/req/{self.name}"
|
|
383
549
|
|
|
384
550
|
@property
|
|
385
551
|
def conf_uri(self) -> str:
|
|
386
|
-
return f"
|
|
552
|
+
return f"{self.spec_uri_base.rstrip('/')}/conf/{self.name}"
|
|
387
553
|
|
|
388
554
|
@model_validator(mode="after")
|
|
389
555
|
def tests_reference_valid_requirements(self) -> ServiceProfile:
|
|
@@ -503,17 +669,18 @@ class ServiceProfile(BaseModel):
|
|
|
503
669
|
for crs_val in coll.crs:
|
|
504
670
|
_check(f"Collection '{coll.id}' crs[]", crs_val, er.supported_crs)
|
|
505
671
|
|
|
506
|
-
# --- crs_details in data_queries → supported_crs ---
|
|
672
|
+
# --- crs_details / crs shorthand in data_queries → supported_crs ---
|
|
507
673
|
if coll.data_queries and er.supported_crs:
|
|
508
674
|
for qt_name, qt_val in coll.data_queries:
|
|
509
675
|
if qt_val is None:
|
|
510
676
|
continue
|
|
511
|
-
|
|
512
|
-
qt_val.link.variables.model_extra
|
|
677
|
+
extra = (
|
|
678
|
+
qt_val.link.variables.model_extra
|
|
513
679
|
if hasattr(qt_val.link.variables, "model_extra")
|
|
514
|
-
else
|
|
515
|
-
)
|
|
516
|
-
|
|
680
|
+
else {}
|
|
681
|
+
) or {}
|
|
682
|
+
crs_details = extra.get("crs_details", []) or []
|
|
683
|
+
for entry in crs_details:
|
|
517
684
|
dq_crs = entry.get("crs") if isinstance(entry, dict) else None
|
|
518
685
|
if dq_crs:
|
|
519
686
|
_check(
|
|
@@ -521,6 +688,41 @@ class ServiceProfile(BaseModel):
|
|
|
521
688
|
dq_crs,
|
|
522
689
|
er.supported_crs,
|
|
523
690
|
)
|
|
691
|
+
# `crs` is an accepted shorthand: a plain list of CRS URIs at
|
|
692
|
+
# the query level, equivalent to crs_details: [{crs: ...}].
|
|
693
|
+
for dq_crs in (extra.get("crs", []) or []):
|
|
694
|
+
if isinstance(dq_crs, str):
|
|
695
|
+
_check(
|
|
696
|
+
f"Collection '{coll.id}' data_queries.{qt_name}.crs",
|
|
697
|
+
dq_crs,
|
|
698
|
+
er.supported_crs,
|
|
699
|
+
)
|
|
700
|
+
return self
|
|
701
|
+
|
|
702
|
+
@model_validator(mode="after")
|
|
703
|
+
def validate_default_output_format(self) -> ServiceProfile:
|
|
704
|
+
"""Ensure a query's default_output_format is one of its output_formats."""
|
|
705
|
+
for coll in self.collections:
|
|
706
|
+
if not coll.data_queries:
|
|
707
|
+
continue
|
|
708
|
+
for qt_name, qt_val in coll.data_queries:
|
|
709
|
+
if qt_val is None:
|
|
710
|
+
continue
|
|
711
|
+
extra = (
|
|
712
|
+
qt_val.link.variables.model_extra
|
|
713
|
+
if hasattr(qt_val.link.variables, "model_extra")
|
|
714
|
+
else {}
|
|
715
|
+
) or {}
|
|
716
|
+
variables = qt_val.link.variables
|
|
717
|
+
default = getattr(variables, "default_output_format", None) or extra.get("default_output_format")
|
|
718
|
+
if default is None:
|
|
719
|
+
continue
|
|
720
|
+
formats = getattr(variables, "output_formats", None) or extra.get("output_formats") or []
|
|
721
|
+
if formats and default not in formats:
|
|
722
|
+
raise ValueError(
|
|
723
|
+
f"Collection '{coll.id}' data_queries.{qt_name}: "
|
|
724
|
+
f"default_output_format '{default}' is not in output_formats {formats}"
|
|
725
|
+
)
|
|
524
726
|
return self
|
|
525
727
|
|
|
526
728
|
@model_validator(mode="after")
|
{oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0/src/oapi_profile_builder.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oapi-profile-builder
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: Authoritative tooling for creating OGC API Service Profiles (EDR, Features)
|
|
5
5
|
Author-email: Shane Mill <shane.mill@noaa.gov>
|
|
6
6
|
License: Apache License
|
|
@@ -290,12 +290,18 @@ abstract_tests: []
|
|
|
290
290
|
| `description` | string | no | Human-readable description of the service profile. Surfaces in the OpenAPI `info.description` and the landing page response schema |
|
|
291
291
|
| `keywords` | list | no | Service-level keywords (e.g. query types, parameter names, domain terms). Surfaces in `info.x-keywords` and the landing page response schema. Distinct from `document_metadata.keywords`, which are for the OGC PDF header |
|
|
292
292
|
| `server_url` | string | no | Documentation only — not written to the profile OpenAPI |
|
|
293
|
+
| `provider` | object | no | Service provider / responsible party. Surfaces in OpenAPI `info.contact` and the document Point of Contact (see below) |
|
|
294
|
+
| `classification` | object | no | Security classification (`level` + `system`). Surfaces as a document banner and `info.x-classification` |
|
|
295
|
+
| `metadata_date` | string | no | Metadata update date (ISO 8601). Surfaces as `info.x-metadata-date` |
|
|
296
|
+
| `resource_service_publish_date` | string | no | Resource/service publication date (ISO 8601). Surfaces as `info.x-resource-publish-date` |
|
|
297
|
+
| `resource_default_locale` | string | no | Default resource locale (e.g. `eng`). Surfaces as `info.x-default-locale` |
|
|
293
298
|
| `allow_post_queries` | bool | no | When `true`, generates `POST` alongside `GET` for all EDR data query endpoints. Defaults to `false` |
|
|
294
299
|
| `collections` | list | yes | One or more EDR collections (see below) |
|
|
295
300
|
| `required_conformance_classes` | list | no | Conformance classes implementations must declare. Defaults to EDR Core |
|
|
296
301
|
| `extent_requirements` | object | no | Profile-level CRS/TRS/VRS constraints (see below) |
|
|
297
302
|
| `output_formats` | list | no | Format name → media type + schema ref mappings |
|
|
298
303
|
| `collection_id_pattern` | string | no | Regex all collection IDs must match |
|
|
304
|
+
| `spec_uri_base` | string | no | Base URI for the requirements/conformance class identifiers. Defaults to the OGC EDR Part 3 namespace; override to publish under another SDO namespace (e.g. DGIWG) |
|
|
299
305
|
| `parameter_name_pattern` | string | no | Regex all `parameter_names` keys must match |
|
|
300
306
|
| `parameter_schema` | object | no | JSON Schema for parameter objects — replaces the default schema in the generated OpenAPI (see below) |
|
|
301
307
|
| `processes` | list | no | OGC API Processes to expose in the OpenAPI |
|
|
@@ -303,7 +309,7 @@ abstract_tests: []
|
|
|
303
309
|
| `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
|
|
304
310
|
| `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
|
|
305
311
|
| `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
|
|
306
|
-
| `document_metadata` | object | no | Metanorma PDF header
|
|
312
|
+
| `document_metadata` | object | no | Metanorma PDF header — doc number/type/subtype, status, editors, submitters, dates, notice, normative references (see below) |
|
|
307
313
|
|
|
308
314
|
---
|
|
309
315
|
|
|
@@ -360,6 +366,22 @@ data_queries:
|
|
|
360
366
|
crs_details:
|
|
361
367
|
- crs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
|
362
368
|
- crs: "http://www.opengis.net/def/crs/EPSG/0/4326"
|
|
369
|
+
cube:
|
|
370
|
+
link:
|
|
371
|
+
href: https://example.com/collections/obs/cube
|
|
372
|
+
rel: data
|
|
373
|
+
variables:
|
|
374
|
+
query_type: cube
|
|
375
|
+
output_formats: [CoverageJSON, GeoTIFF]
|
|
376
|
+
# default_output_format: response format used when the `f` query
|
|
377
|
+
# parameter is omitted. Must be one of output_formats. Sets the
|
|
378
|
+
# `default` on the generated OpenAPI `f` parameter for this query.
|
|
379
|
+
default_output_format: GeoTIFF
|
|
380
|
+
# crs: shorthand for crs_details — a plain list of CRS URIs.
|
|
381
|
+
# Validated against extent_requirements.supported_crs.
|
|
382
|
+
crs:
|
|
383
|
+
- "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
|
384
|
+
- "http://www.opengis.net/def/crs/EPSG/0/4326"
|
|
363
385
|
```
|
|
364
386
|
|
|
365
387
|
#### `parameter_names` example
|
|
@@ -536,16 +558,39 @@ pubsub:
|
|
|
536
558
|
|
|
537
559
|
### `document_metadata`
|
|
538
560
|
|
|
539
|
-
Required only when compiling a PDF with `--pdf`.
|
|
561
|
+
Required only when compiling a PDF with `--pdf`. Fields map to Metanorma OGC document attributes.
|
|
540
562
|
|
|
541
563
|
```yaml
|
|
542
564
|
document_metadata:
|
|
543
565
|
doc_number: "25-myprofile"
|
|
544
|
-
|
|
566
|
+
doc_type: draft-standard # Metanorma :doctype: (draft-standard | standard | best-practice | engineering-report | ...)
|
|
567
|
+
doc_subtype: implementation # implementation | best-practice | engineering-report | profile
|
|
568
|
+
status: approved # Metanorma :status:/:docstage: (swg-draft | public-rfc | approved | ...)
|
|
545
569
|
editors:
|
|
546
570
|
- Jane Smith
|
|
571
|
+
# submitters: structured table for page iv — each row carries its own affiliation.
|
|
572
|
+
# When provided, takes precedence over the editors/submitting_orgs fallback.
|
|
573
|
+
submitters:
|
|
574
|
+
- name: Jane Smith
|
|
575
|
+
affiliation: My Organization
|
|
576
|
+
role: editor
|
|
577
|
+
- name: Standards Working Group
|
|
578
|
+
affiliation: My SDO
|
|
547
579
|
submitting_orgs:
|
|
548
580
|
- My Organization
|
|
581
|
+
# Title-page dates (YYYY-MM-DD). Omitted dates fall back to {copyright_year}-01-01.
|
|
582
|
+
submission_date: "2026-05-01" # → :received-date:
|
|
583
|
+
approval_date: "2026-06-15" # → :issued-date:
|
|
584
|
+
publication_date: "2026-07-01" # → :published-date:
|
|
585
|
+
# Custom notice paragraph rendered in the front matter. Note: the OGC cover-page
|
|
586
|
+
# legal notice itself is generated by Metanorma from doc_type/status.
|
|
587
|
+
notice: >
|
|
588
|
+
This is a DRAFT standard and is subject to change.
|
|
589
|
+
# Additional normative references appended to the References section.
|
|
590
|
+
normative_references:
|
|
591
|
+
- anchor: DGIWG-250
|
|
592
|
+
citation: DGIWG 250
|
|
593
|
+
title: DGIWG GeoTIFF Profile
|
|
549
594
|
keywords:
|
|
550
595
|
- ogcdoc
|
|
551
596
|
- OGC API
|
|
@@ -554,6 +599,64 @@ document_metadata:
|
|
|
554
599
|
external_id: http://www.opengis.net/doc/dp/my-profile/1.0
|
|
555
600
|
```
|
|
556
601
|
|
|
602
|
+
The cover-page sub-title (e.g. "Draft Standard — Implementation — Approved") is produced by Metanorma from the `doc_type`, `doc_subtype`, and `status` combination.
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
### `provider`, `classification`, and provenance metadata
|
|
607
|
+
|
|
608
|
+
Optional service-level metadata. Each may also be set per-collection (the collection value overrides the profile root for that collection and is round-tripped into `profile_config.json`).
|
|
609
|
+
|
|
610
|
+
```yaml
|
|
611
|
+
provider:
|
|
612
|
+
name: Austrian Military
|
|
613
|
+
url: "https://www.bmlv.gv.at/english/forces/organisation.shtml"
|
|
614
|
+
contact:
|
|
615
|
+
email: presse@bmlvs.gv.at
|
|
616
|
+
phone: "+43 ..."
|
|
617
|
+
hours: Monday to Friday 08:00 - 17:00
|
|
618
|
+
instructions: During hours of Service
|
|
619
|
+
address: "Ministry of Defence, Rossauer Lände 1"
|
|
620
|
+
postalcode: A-1090
|
|
621
|
+
city: Vienna
|
|
622
|
+
country: Austria
|
|
623
|
+
|
|
624
|
+
classification:
|
|
625
|
+
level: "NATO RESTRICTED (NR)"
|
|
626
|
+
system: NATO
|
|
627
|
+
|
|
628
|
+
metadata_date: "2026-06-25T07:31Z"
|
|
629
|
+
resource_service_publish_date: "2026-06-24T14:10Z"
|
|
630
|
+
resource_default_locale: eng
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Where these surface:
|
|
634
|
+
|
|
635
|
+
- `provider` → OpenAPI `info.contact` (`name`/`url`/`email`; the remaining contact fields become `x-` extensions) and a **Point of Contact** section in the generated document.
|
|
636
|
+
- `classification` → a **security classification banner** in the document Abstract and `info.x-classification` in the OpenAPI.
|
|
637
|
+
- `metadata_date` / `resource_service_publish_date` / `resource_default_locale` → `info.x-metadata-date` / `info.x-resource-publish-date` / `info.x-default-locale`.
|
|
638
|
+
|
|
639
|
+
> The profile OpenAPI is implementation-independent (per EDR Part 3 REQ_publishing), so these describe the profile/data provenance rather than a specific deployment endpoint.
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
### `spec_uri_base`
|
|
644
|
+
|
|
645
|
+
By default, the requirements class and conformance class identifiers are published under the OGC API - EDR Part 3 namespace:
|
|
646
|
+
|
|
647
|
+
```
|
|
648
|
+
http://www.opengis.net/spec/ogcapi-edr-3/1.0/req/<name>
|
|
649
|
+
http://www.opengis.net/spec/ogcapi-edr-3/1.0/conf/<name>
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Set `spec_uri_base` to publish under a different SDO namespace. This updates the OpenAPI `x-ogc-profile` link, the requirements/conformance AsciiDoc identifiers, and the conformance section:
|
|
653
|
+
|
|
654
|
+
```yaml
|
|
655
|
+
spec_uri_base: "https://schemas.dgiwg.org/edr/1.0"
|
|
656
|
+
# → https://schemas.dgiwg.org/edr/1.0/req/<name>
|
|
657
|
+
# → https://schemas.dgiwg.org/edr/1.0/conf/<name>
|
|
658
|
+
```
|
|
659
|
+
|
|
557
660
|
---
|
|
558
661
|
|
|
559
662
|
## Validation Rules
|
|
@@ -564,10 +667,12 @@ The tool enforces these rules at build time. Violations produce clear error mess
|
|
|
564
667
|
|---|---|
|
|
565
668
|
| `name` format | Must match `^[a-z0-9_]+$` |
|
|
566
669
|
| No duplicate collection IDs | Across the whole profile |
|
|
567
|
-
| `extent_requirements` requires CRS spec |
|
|
568
|
-
| Collection CRS validated | Each `extent.spatial.crs` checked against `
|
|
569
|
-
| Collection TRS validated | Each `extent.temporal.trs` checked against `
|
|
570
|
-
| `crs_details` validated | Each `data_queries.*.variables.crs_details[].crs` checked against
|
|
670
|
+
| `extent_requirements` requires CRS spec | At least one of `extent_crs` or `supported_crs` must be set (each uses `allowed` or `pattern`) |
|
|
671
|
+
| Collection CRS validated | Each `extent.spatial.crs` checked against `extent_crs`; `crs[]` and `crs_details` checked against `supported_crs` |
|
|
672
|
+
| Collection TRS validated | Each `extent.temporal.trs` checked against `extent_trs` |
|
|
673
|
+
| `crs_details` / `crs` validated | Each `data_queries.*.variables.crs_details[].crs` and `crs[]` checked against `supported_crs` |
|
|
674
|
+
| `default_output_format` validated | Each `data_queries.*.variables.default_output_format` must be one of that query's `output_formats` |
|
|
675
|
+
| Document dates format | `submission_date` / `approval_date` / `publication_date` must be `YYYY-MM-DD` |
|
|
571
676
|
| Parameters need `unit` + `observedProperty` | Required by OGC API - EDR Part 3 |
|
|
572
677
|
| `parameter_name_pattern` enforced | All `parameter_names` keys must match if set |
|
|
573
678
|
| `collection_id_pattern` enforced | All collection IDs must match if set |
|
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/cite_features.py
RENAMED
|
File without changes
|
{oapi_profile_builder-3.0.0 → oapi_profile_builder-3.1.0}/src/oapi_profile_builder/compile.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|