oapi-profile-builder 3.6.1__tar.gz → 3.6.3__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.1/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.6.3}/PKG-INFO +23 -7
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/README.md +22 -6
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/pyproject.toml +1 -1
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/cover.py +63 -22
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/generate.py +72 -45
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/models.py +5 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3/src/oapi_profile_builder.egg-info}/PKG-INFO +23 -7
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/LICENSE +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/setup.cfg +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/__init__.py +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/cite.py +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/cite_features.py +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/cli.py +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/compile.py +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/server_validation.py +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
- {oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder.egg-info/top_level.txt +0 -0
{oapi_profile_builder-3.6.1/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.6.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oapi-profile-builder
|
|
3
|
-
Version: 3.6.
|
|
3
|
+
Version: 3.6.3
|
|
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
|
|
@@ -47,7 +47,7 @@ Dynamic: license-file
|
|
|
47
47
|
|
|
48
48
|
# OGC API Service Profile Builder
|
|
49
49
|
|
|
50
|
-
Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.1.0, AsyncAPI, AsciiDoc requirements and conformance tests, and a publication-ready Metanorma PDF with optional custom (e.g. DGIWG) branding.
|
|
50
|
+
Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.0.0/3.1.0, AsyncAPI, AsciiDoc requirements and conformance tests, and a publication-ready Metanorma PDF with optional custom (e.g. DGIWG) branding.
|
|
51
51
|
|
|
52
52
|
[](https://pypi.org/project/oapi-profile-builder/)
|
|
53
53
|
[](LICENSE)
|
|
@@ -57,7 +57,9 @@ Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — O
|
|
|
57
57
|
## Quick Start
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
|
|
60
|
+
# Base install. Adds Pillow (cover image generation) with the [pdf] extra —
|
|
61
|
+
# required whenever your profile sets document_metadata.cover.logo.
|
|
62
|
+
pip install 'oapi-profile-builder[pdf]'
|
|
61
63
|
|
|
62
64
|
# Copy an example config and edit it
|
|
63
65
|
cp examples/minimal_profile.yaml my_profile.yaml
|
|
@@ -70,7 +72,7 @@ That's it. The `output/` directory will contain:
|
|
|
70
72
|
|
|
71
73
|
```
|
|
72
74
|
output/
|
|
73
|
-
├── openapi.yaml # OpenAPI 3.1.0 — ready for Swagger UI, Redoc, schemathesis
|
|
75
|
+
├── openapi.yaml # OpenAPI 3.0.0 or 3.1.0 — ready for Swagger UI, Redoc, schemathesis
|
|
74
76
|
├── profile_config.json # Round-trip serialized profile
|
|
75
77
|
├── document.adoc # Metanorma root document
|
|
76
78
|
├── sections/ # Abstract, Preface, Scope, Conformance, References, Terms
|
|
@@ -186,8 +188,18 @@ OGC API - EDR CITE Results
|
|
|
186
188
|
Anyone with a GitHub account can generate artifacts from their own config without cloning or installing anything:
|
|
187
189
|
|
|
188
190
|
1. Open a new issue using the **"Generate profile artifacts"** template ([New issue](../../issues/new/choose)).
|
|
189
|
-
2. Paste your profile YAML into the config field
|
|
190
|
-
3.
|
|
191
|
+
2. Paste your profile YAML into the config field.
|
|
192
|
+
3. If your profile references local files (typically a cover logo via `document_metadata.cover.logo`), drag-and-drop them into the **"Additional assets (optional)"** field. Reference each uploaded file in your YAML as `uploads/<original-filename>` — for example:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
document_metadata:
|
|
196
|
+
cover:
|
|
197
|
+
logo: uploads/my_logo.png
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The workflow downloads each dropped file into `./uploads/` before generation. It parses your YAML to figure out the target filename, so images that GitHub renders as an `<img>` tag with a UUID URL still land at the exact path your config references. Tip: to skip the upload entirely for the DGIWG logo, point at the copy that already ships in this repo: `logo: examples/assets/DGIWG_logo.png`.
|
|
201
|
+
4. Tick "Compile a PDF?" if you also want a Metanorma PDF, then submit.
|
|
202
|
+
5. A workflow validates the config, generates the artifacts, and comments on the issue with a link to download them as a zip (retained for 14 days).
|
|
191
203
|
|
|
192
204
|
The workflow only runs the generator over your declarative config — it never executes user-supplied code — and comments back on both success and failure. Don't paste secrets: the artifacts are attached to a public workflow run.
|
|
193
205
|
|
|
@@ -366,6 +378,7 @@ abstract_tests: []
|
|
|
366
378
|
| `processes` | list | no | OGC API Processes to expose in the OpenAPI |
|
|
367
379
|
| `requirements` | list | no | Normative requirements for the AsciiDoc/PDF |
|
|
368
380
|
| `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
|
|
381
|
+
| `abstract_tests[].method` | string | no | Optional test method description |
|
|
369
382
|
| `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
|
|
370
383
|
| `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
|
|
371
384
|
| `paging` | object | no | Features `/items` paging — adds a validated `limit` query parameter to the generated OpenAPI (see below) |
|
|
@@ -660,6 +673,7 @@ requirements:
|
|
|
660
673
|
abstract_tests:
|
|
661
674
|
- id: position-coveragejson # must equal requirement_id
|
|
662
675
|
requirement_id: position-coveragejson
|
|
676
|
+
method: Manual inspection of the /position response.
|
|
663
677
|
steps:
|
|
664
678
|
- Send GET /collections/{id}/position?coords=POINT(lon lat).
|
|
665
679
|
- Verify Content-Type is application/prs.coverage+json.
|
|
@@ -812,7 +826,9 @@ Two `document_metadata` blocks let you rebrand the PDF (e.g. for DGIWG) without
|
|
|
812
826
|
document_metadata:
|
|
813
827
|
doc_number: "DGIWG 134"
|
|
814
828
|
cover:
|
|
815
|
-
logo: assets/
|
|
829
|
+
logo: examples/assets/DGIWG_logo.png # path relative to the working directory (or absolute).
|
|
830
|
+
# From the self-service issue form use uploads/<file>
|
|
831
|
+
# instead and drag-drop the file into "Additional assets".
|
|
816
832
|
tagline: Delivering Military Advantage through multi-national geospatial interoperability
|
|
817
833
|
background: "#FFFFFF" # optional cover background (default white)
|
|
818
834
|
text_color: "#1F3864" # optional cover text colour (default black)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OGC API Service Profile Builder
|
|
2
2
|
|
|
3
|
-
Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.1.0, AsyncAPI, AsciiDoc requirements and conformance tests, and a publication-ready Metanorma PDF with optional custom (e.g. DGIWG) branding.
|
|
3
|
+
Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.0.0/3.1.0, AsyncAPI, AsciiDoc requirements and conformance tests, and a publication-ready Metanorma PDF with optional custom (e.g. DGIWG) branding.
|
|
4
4
|
|
|
5
5
|
[](https://pypi.org/project/oapi-profile-builder/)
|
|
6
6
|
[](LICENSE)
|
|
@@ -10,7 +10,9 @@ Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — O
|
|
|
10
10
|
## Quick Start
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
|
|
13
|
+
# Base install. Adds Pillow (cover image generation) with the [pdf] extra —
|
|
14
|
+
# required whenever your profile sets document_metadata.cover.logo.
|
|
15
|
+
pip install 'oapi-profile-builder[pdf]'
|
|
14
16
|
|
|
15
17
|
# Copy an example config and edit it
|
|
16
18
|
cp examples/minimal_profile.yaml my_profile.yaml
|
|
@@ -23,7 +25,7 @@ That's it. The `output/` directory will contain:
|
|
|
23
25
|
|
|
24
26
|
```
|
|
25
27
|
output/
|
|
26
|
-
├── openapi.yaml # OpenAPI 3.1.0 — ready for Swagger UI, Redoc, schemathesis
|
|
28
|
+
├── openapi.yaml # OpenAPI 3.0.0 or 3.1.0 — ready for Swagger UI, Redoc, schemathesis
|
|
27
29
|
├── profile_config.json # Round-trip serialized profile
|
|
28
30
|
├── document.adoc # Metanorma root document
|
|
29
31
|
├── sections/ # Abstract, Preface, Scope, Conformance, References, Terms
|
|
@@ -139,8 +141,18 @@ OGC API - EDR CITE Results
|
|
|
139
141
|
Anyone with a GitHub account can generate artifacts from their own config without cloning or installing anything:
|
|
140
142
|
|
|
141
143
|
1. Open a new issue using the **"Generate profile artifacts"** template ([New issue](../../issues/new/choose)).
|
|
142
|
-
2. Paste your profile YAML into the config field
|
|
143
|
-
3.
|
|
144
|
+
2. Paste your profile YAML into the config field.
|
|
145
|
+
3. If your profile references local files (typically a cover logo via `document_metadata.cover.logo`), drag-and-drop them into the **"Additional assets (optional)"** field. Reference each uploaded file in your YAML as `uploads/<original-filename>` — for example:
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
document_metadata:
|
|
149
|
+
cover:
|
|
150
|
+
logo: uploads/my_logo.png
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The workflow downloads each dropped file into `./uploads/` before generation. It parses your YAML to figure out the target filename, so images that GitHub renders as an `<img>` tag with a UUID URL still land at the exact path your config references. Tip: to skip the upload entirely for the DGIWG logo, point at the copy that already ships in this repo: `logo: examples/assets/DGIWG_logo.png`.
|
|
154
|
+
4. Tick "Compile a PDF?" if you also want a Metanorma PDF, then submit.
|
|
155
|
+
5. A workflow validates the config, generates the artifacts, and comments on the issue with a link to download them as a zip (retained for 14 days).
|
|
144
156
|
|
|
145
157
|
The workflow only runs the generator over your declarative config — it never executes user-supplied code — and comments back on both success and failure. Don't paste secrets: the artifacts are attached to a public workflow run.
|
|
146
158
|
|
|
@@ -319,6 +331,7 @@ abstract_tests: []
|
|
|
319
331
|
| `processes` | list | no | OGC API Processes to expose in the OpenAPI |
|
|
320
332
|
| `requirements` | list | no | Normative requirements for the AsciiDoc/PDF |
|
|
321
333
|
| `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
|
|
334
|
+
| `abstract_tests[].method` | string | no | Optional test method description |
|
|
322
335
|
| `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
|
|
323
336
|
| `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
|
|
324
337
|
| `paging` | object | no | Features `/items` paging — adds a validated `limit` query parameter to the generated OpenAPI (see below) |
|
|
@@ -613,6 +626,7 @@ requirements:
|
|
|
613
626
|
abstract_tests:
|
|
614
627
|
- id: position-coveragejson # must equal requirement_id
|
|
615
628
|
requirement_id: position-coveragejson
|
|
629
|
+
method: Manual inspection of the /position response.
|
|
616
630
|
steps:
|
|
617
631
|
- Send GET /collections/{id}/position?coords=POINT(lon lat).
|
|
618
632
|
- Verify Content-Type is application/prs.coverage+json.
|
|
@@ -765,7 +779,9 @@ Two `document_metadata` blocks let you rebrand the PDF (e.g. for DGIWG) without
|
|
|
765
779
|
document_metadata:
|
|
766
780
|
doc_number: "DGIWG 134"
|
|
767
781
|
cover:
|
|
768
|
-
logo: assets/
|
|
782
|
+
logo: examples/assets/DGIWG_logo.png # path relative to the working directory (or absolute).
|
|
783
|
+
# From the self-service issue form use uploads/<file>
|
|
784
|
+
# instead and drag-drop the file into "Additional assets".
|
|
769
785
|
tagline: Delivering Military Advantage through multi-national geospatial interoperability
|
|
770
786
|
background: "#FFFFFF" # optional cover background (default white)
|
|
771
787
|
text_color: "#1F3864" # optional cover text colour (default black)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "oapi-profile-builder"
|
|
7
|
-
version = "3.6.
|
|
7
|
+
version = "3.6.3"
|
|
8
8
|
description = "Authoritative tooling for creating OGC API Service Profiles (EDR, Features)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -43,9 +43,9 @@ _PAGE_H = 1754
|
|
|
43
43
|
_COVER_FILENAME = "cover.png"
|
|
44
44
|
|
|
45
45
|
# Default font family (bundled with Pillow, resolved by name).
|
|
46
|
-
_DEFAULT_REGULAR = "DejaVuSans
|
|
47
|
-
_DEFAULT_BOLD = "DejaVuSans-Bold
|
|
48
|
-
_DEFAULT_ITALIC = "DejaVuSans
|
|
46
|
+
_DEFAULT_REGULAR = "DejaVuSans"
|
|
47
|
+
_DEFAULT_BOLD = "DejaVuSans-Bold"
|
|
48
|
+
_DEFAULT_ITALIC = "DejaVuSans:style=Oblique"
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def _resolve_font_file(spec: str | None, *, bold: bool = False, italic: bool = False) -> str | None:
|
|
@@ -62,20 +62,40 @@ def _resolve_font_file(spec: str | None, *, bold: bool = False, italic: bool = F
|
|
|
62
62
|
if p.is_file():
|
|
63
63
|
return str(p)
|
|
64
64
|
if shutil.which("fc-match"):
|
|
65
|
-
|
|
66
|
-
if
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
styles
|
|
70
|
-
|
|
65
|
+
# If the spec already contains a colon, treat it as a full fontconfig query
|
|
66
|
+
if ":" in spec:
|
|
67
|
+
query = spec
|
|
68
|
+
else:
|
|
69
|
+
styles = []
|
|
70
|
+
if bold:
|
|
71
|
+
styles.append("Bold")
|
|
72
|
+
if italic:
|
|
73
|
+
styles.append("Italic")
|
|
74
|
+
query = spec if not styles else f"{spec}:style={' '.join(styles)}"
|
|
75
|
+
|
|
71
76
|
try:
|
|
77
|
+
# Try the primary query
|
|
72
78
|
out = subprocess.run(
|
|
73
79
|
["fc-match", "-f", "%{file}", query],
|
|
74
80
|
capture_output=True, text=True, timeout=5, check=False,
|
|
75
81
|
)
|
|
76
82
|
path = out.stdout.strip()
|
|
77
83
|
if path and Path(path).is_file():
|
|
78
|
-
|
|
84
|
+
# Verify that the result matches the requested style if possible.
|
|
85
|
+
# If we asked for Italic but got a Regular file, fc-match just fell back.
|
|
86
|
+
if italic and "Italic" not in path and "Oblique" not in path:
|
|
87
|
+
# Try a second attempt explicitly asking for Oblique if Italic failed
|
|
88
|
+
if ":" not in spec:
|
|
89
|
+
query_oblique = f"{spec}:style=Oblique"
|
|
90
|
+
out = subprocess.run(
|
|
91
|
+
["fc-match", "-f", "%{file}", query_oblique],
|
|
92
|
+
capture_output=True, text=True, timeout=5, check=False,
|
|
93
|
+
)
|
|
94
|
+
path = out.stdout.strip()
|
|
95
|
+
if path and Path(path).is_file() and ("Italic" in path or "Oblique" in path):
|
|
96
|
+
return path
|
|
97
|
+
else:
|
|
98
|
+
return path
|
|
79
99
|
except Exception:
|
|
80
100
|
pass
|
|
81
101
|
return None
|
|
@@ -89,22 +109,36 @@ class _FontSet:
|
|
|
89
109
|
ultimately fall back to the bundled DejaVu family.
|
|
90
110
|
"""
|
|
91
111
|
|
|
92
|
-
def __init__(self, cover=None):
|
|
112
|
+
def __init__(self, cover=None, metadata=None):
|
|
93
113
|
self.regular = getattr(cover, "font_regular", None) if cover else None
|
|
94
114
|
self.bold = getattr(cover, "font_bold", None) if cover else None
|
|
95
115
|
self.italic = getattr(cover, "font_italic", None) if cover else None
|
|
116
|
+
|
|
117
|
+
# Fallback to metadata-level font overrides if set
|
|
118
|
+
if not self.regular and metadata:
|
|
119
|
+
self.regular = getattr(metadata, "body_font", None)
|
|
120
|
+
if not self.bold and metadata:
|
|
121
|
+
self.bold = getattr(metadata, "header_font", None)
|
|
96
122
|
|
|
97
123
|
def _font(self, size: int, *, bold: bool = False, italic: bool = False):
|
|
98
124
|
from PIL import ImageFont
|
|
99
125
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
_resolve_font_file(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
126
|
+
candidates = []
|
|
127
|
+
# 1. Try explicit style override (path or family name)
|
|
128
|
+
style_spec = self.bold if bold else (self.italic if italic else None)
|
|
129
|
+
if style_spec:
|
|
130
|
+
candidates.append(_resolve_font_file(style_spec, bold=bold, italic=italic))
|
|
131
|
+
|
|
132
|
+
# 2. Try deriving from the regular spec if it is a family name
|
|
133
|
+
if (bold or italic) and self.regular and not Path(self.regular).expanduser().is_file():
|
|
134
|
+
candidates.append(_resolve_font_file(self.regular, bold=bold, italic=italic))
|
|
135
|
+
|
|
136
|
+
# 3. Try standard defaults for the requested style
|
|
137
|
+
candidates.append(_DEFAULT_BOLD if bold else (_DEFAULT_ITALIC if italic else _DEFAULT_REGULAR))
|
|
138
|
+
|
|
139
|
+
# 4. Fallback to the regular font spec (even if not the right style)
|
|
140
|
+
candidates.append(_resolve_font_file(self.regular))
|
|
141
|
+
|
|
108
142
|
for cand in candidates:
|
|
109
143
|
if not cand:
|
|
110
144
|
continue
|
|
@@ -160,7 +194,7 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
|
|
|
160
194
|
|
|
161
195
|
bg = cover.background or "#FFFFFF"
|
|
162
196
|
fg = cover.text_color or "#000000"
|
|
163
|
-
fonts = _FontSet(cover)
|
|
197
|
+
fonts = _FontSet(cover, m)
|
|
164
198
|
|
|
165
199
|
page = Image.new("RGB", (_PAGE_W, _PAGE_H), bg)
|
|
166
200
|
draw = ImageDraw.Draw(page)
|
|
@@ -191,11 +225,18 @@ def build_cover_image(profile, output_dir: Path) -> str | None:
|
|
|
191
225
|
# --- Tagline (italic) ---
|
|
192
226
|
if cover.tagline:
|
|
193
227
|
tagline_fs = getattr(cover, "tagline_font_size", 30)
|
|
194
|
-
|
|
228
|
+
tagline_font = fonts.italic_font(tagline_fs)
|
|
229
|
+
for line in _wrap(draw, cover.tagline, tagline_font, _PAGE_W - 240):
|
|
230
|
+
y = centered(line, tagline_font, y, fg) + 10
|
|
231
|
+
y += 50
|
|
195
232
|
|
|
196
233
|
# --- Document number ---
|
|
197
234
|
if m and m.doc_number:
|
|
198
|
-
|
|
235
|
+
doc_num_font = fonts.regular_font(46, bold=True)
|
|
236
|
+
for line in _wrap(draw, m.doc_number, doc_num_font, _PAGE_W - 240):
|
|
237
|
+
y = centered(line, doc_num_font, y, fg) + 16
|
|
238
|
+
y += 24
|
|
239
|
+
|
|
199
240
|
|
|
200
241
|
# --- Edition & date ---
|
|
201
242
|
# Per DGIWG feedback these two lines are both bold and set a little smaller
|
{oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/generate.py
RENAMED
|
@@ -41,6 +41,16 @@ import yaml
|
|
|
41
41
|
|
|
42
42
|
from edr_pydantic.collections import Collection
|
|
43
43
|
from oapi_profile_builder.models import ServiceProfile
|
|
44
|
+
def _const(val, version: str) -> dict:
|
|
45
|
+
if version == "3.1.0":
|
|
46
|
+
return {"const": val}
|
|
47
|
+
return {"enum": [val]}
|
|
48
|
+
|
|
49
|
+
def _contains(schema: dict, version: str) -> dict:
|
|
50
|
+
if version == "3.1.0":
|
|
51
|
+
return {"contains": schema}
|
|
52
|
+
return {}
|
|
53
|
+
|
|
44
54
|
|
|
45
55
|
|
|
46
56
|
class _NoAliasDumper(yaml.Dumper):
|
|
@@ -125,30 +135,30 @@ _LINK_SCHEMA = {
|
|
|
125
135
|
_LINKS_ARRAY = {"type": "array", "items": _LINK_SCHEMA}
|
|
126
136
|
|
|
127
137
|
# Landing page response with required profile link per REQ_publishing
|
|
128
|
-
def _landing_page_schema(profile_uri: str, title: str = "", description: str = "", keywords: list | None = None) -> dict:
|
|
138
|
+
def _landing_page_schema(profile_uri: str, version: str, title: str = "", description: str = "", keywords: list | None = None) -> dict:
|
|
129
139
|
schema_props: dict = {
|
|
130
140
|
"links": {
|
|
131
141
|
"type": "array",
|
|
132
142
|
"items": _LINK_SCHEMA,
|
|
133
|
-
|
|
143
|
+
**(_contains({
|
|
134
144
|
"type": "object",
|
|
135
145
|
"required": ["href", "rel"],
|
|
136
146
|
"properties": {
|
|
137
|
-
"rel":
|
|
138
|
-
"href":
|
|
147
|
+
"rel": _const("profile", version),
|
|
148
|
+
"href": _const(profile_uri, version),
|
|
139
149
|
},
|
|
140
|
-
},
|
|
150
|
+
}, version) if version == "3.1.0" else {}),
|
|
141
151
|
},
|
|
142
152
|
}
|
|
143
153
|
if title:
|
|
144
|
-
schema_props["title"] = {"type": "string",
|
|
154
|
+
schema_props["title"] = {"type": "string", **_const(title, version)}
|
|
145
155
|
if description:
|
|
146
156
|
schema_props["description"] = {"type": "string"}
|
|
147
157
|
if keywords:
|
|
148
158
|
schema_props["keywords"] = {
|
|
149
159
|
"type": "array",
|
|
150
160
|
"items": {"type": "string"},
|
|
151
|
-
"
|
|
161
|
+
**(_contains({"enum": keywords}, version) if version == "3.1.0" else {"items": {"type": "string", "enum": keywords}}),
|
|
152
162
|
}
|
|
153
163
|
|
|
154
164
|
return {
|
|
@@ -182,27 +192,30 @@ _R200_CONFORMANCE = {
|
|
|
182
192
|
}
|
|
183
193
|
}
|
|
184
194
|
_R200_COLLECTION = {"description": "Collection metadata", "content": {"application/json": {"schema": {"type": "object", "required": ["id"], "properties": {"id": {"type": "string"}, "title": {"type": "string"}, "description": {"type": "string"}, "links": _LINKS_ARRAY}}}}}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
|
|
194
|
-
"
|
|
195
|
-
|
|
195
|
+
def _features_schema(version: str) -> dict:
|
|
196
|
+
return {
|
|
197
|
+
"description": "Feature collection",
|
|
198
|
+
"content": {
|
|
199
|
+
"application/json": {
|
|
200
|
+
"schema": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"properties": {
|
|
203
|
+
"type": {"type": "string", **_const("FeatureCollection", version)},
|
|
204
|
+
"features": {
|
|
205
|
+
"type": "array",
|
|
206
|
+
"items": {"type": "object"},
|
|
207
|
+
},
|
|
196
208
|
},
|
|
197
209
|
},
|
|
198
210
|
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
211
|
+
},
|
|
212
|
+
}
|
|
202
213
|
|
|
203
214
|
def _locations_response_schema(profile: "ServiceProfile | None") -> dict:
|
|
204
215
|
"""Build the 200 response for a /locations query.
|
|
205
216
|
|
|
217
|
+
version = profile.openapi_version if profile else "3.0.0"
|
|
218
|
+
|
|
206
219
|
Per the EDR locations-response requirement (and the MetOcean profile's
|
|
207
220
|
/req/core/locations_query_response_format), the response is a GeoJSON
|
|
208
221
|
FeatureCollection where every feature carries a string ``id`` and a
|
|
@@ -210,6 +223,7 @@ def _locations_response_schema(profile: "ServiceProfile | None") -> dict:
|
|
|
210
223
|
properties named in ``profile.locations_feature_required_properties`` are
|
|
211
224
|
marked required so the generated OpenAPI enforces them.
|
|
212
225
|
"""
|
|
226
|
+
version = profile.openapi_version if profile else "3.0.0"
|
|
213
227
|
required_props = list(getattr(profile, "locations_feature_required_properties", None) or [])
|
|
214
228
|
properties_schema: dict = {
|
|
215
229
|
"type": "object",
|
|
@@ -223,7 +237,7 @@ def _locations_response_schema(profile: "ServiceProfile | None") -> dict:
|
|
|
223
237
|
"type": "object",
|
|
224
238
|
"required": ["type", "id", "properties"],
|
|
225
239
|
"properties": {
|
|
226
|
-
"type": {"type": "string", "
|
|
240
|
+
"type": {"type": "string", **_const("Feature", version)},
|
|
227
241
|
"id": {"type": "string", "description": "Location identifier usable at /locations/{locId}"},
|
|
228
242
|
"geometry": {"type": "object", "description": "GeoJSON geometry of the location"},
|
|
229
243
|
"properties": properties_schema,
|
|
@@ -237,7 +251,7 @@ def _locations_response_schema(profile: "ServiceProfile | None") -> dict:
|
|
|
237
251
|
"type": "object",
|
|
238
252
|
"required": ["type", "features"],
|
|
239
253
|
"properties": {
|
|
240
|
-
"type": {"type": "string", "
|
|
254
|
+
"type": {"type": "string", **_const("FeatureCollection", version)},
|
|
241
255
|
"features": {"type": "array", "items": feature_schema},
|
|
242
256
|
},
|
|
243
257
|
}
|
|
@@ -267,11 +281,11 @@ def _coverage_response(coll: Collection, profile: "ServiceProfile | None") -> di
|
|
|
267
281
|
media_type = "application/prs.coverage+json"
|
|
268
282
|
# Also accept the newer media type
|
|
269
283
|
alt_media = "application/vnd.cov+json"
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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"}
|
|
275
289
|
content[media_type] = {"schema": schema}
|
|
276
290
|
content[alt_media] = {"schema": schema}
|
|
277
291
|
elif fmt_name == "GeoJSON":
|
|
@@ -517,6 +531,7 @@ def _collection_response_schema(coll: Collection,
|
|
|
517
531
|
- Full parameter_names objects with all fields
|
|
518
532
|
- parameter_name_pattern as propertyNames constraint
|
|
519
533
|
"""
|
|
534
|
+
version = profile.openapi_version if profile else "3.0.0"
|
|
520
535
|
crs_schema: dict = {"type": "string"} # extent.spatial.crs
|
|
521
536
|
trs_schema: dict = {"type": "string"} # extent.temporal.trs
|
|
522
537
|
vrs_schema: dict = {"type": "string"} # extent.vertical.vrs
|
|
@@ -549,13 +564,17 @@ def _collection_response_schema(coll: Collection,
|
|
|
549
564
|
import copy
|
|
550
565
|
param_item_schema = copy.deepcopy(profile.parameter_schema)
|
|
551
566
|
else:
|
|
552
|
-
param_item_schema = _parameter_schema()
|
|
567
|
+
param_item_schema = _parameter_schema(profile.openapi_version)
|
|
553
568
|
|
|
554
569
|
param_names_schema: dict = {
|
|
555
570
|
"type": "object",
|
|
556
571
|
"additionalProperties": param_item_schema,
|
|
557
572
|
}
|
|
558
|
-
if
|
|
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:
|
|
559
578
|
param_names_schema["propertyNames"] = {
|
|
560
579
|
"type": "string",
|
|
561
580
|
"pattern": profile.parameter_name_pattern,
|
|
@@ -614,7 +633,7 @@ def _collection_response_schema(coll: Collection,
|
|
|
614
633
|
"type": "array",
|
|
615
634
|
"items": {
|
|
616
635
|
"type": "array",
|
|
617
|
-
"items": {
|
|
636
|
+
"items": {"type": "string", "format": "date-time", "nullable": True} if version == "3.0.0" else {
|
|
618
637
|
"oneOf": [
|
|
619
638
|
{"type": "string", "format": "date-time"},
|
|
620
639
|
{"type": "null"},
|
|
@@ -734,13 +753,13 @@ def _collection_response_schema(coll: Collection,
|
|
|
734
753
|
}
|
|
735
754
|
|
|
736
755
|
|
|
737
|
-
def _parameter_schema() -> dict:
|
|
756
|
+
def _parameter_schema(version: str) -> dict:
|
|
738
757
|
"""OpenAPI schema for a single EDR Parameter object."""
|
|
739
758
|
return {
|
|
740
759
|
"type": "object",
|
|
741
760
|
"required": ["type", "observedProperty"],
|
|
742
761
|
"properties": {
|
|
743
|
-
"type": {"type": "string", "
|
|
762
|
+
"type": {"type": "string", **_const("Parameter", version)},
|
|
744
763
|
"id": {"type": "string"},
|
|
745
764
|
"label": {"type": "string"},
|
|
746
765
|
"description": {"type": "string"},
|
|
@@ -930,7 +949,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
930
949
|
"tags": [tag],
|
|
931
950
|
"parameters": items_params,
|
|
932
951
|
"responses": {
|
|
933
|
-
"200":
|
|
952
|
+
"200": _features_schema(profile.openapi_version if profile else "3.0.0"),
|
|
934
953
|
"400": _ERR_400, "404": _ERR_404, "500": _ERR_500,
|
|
935
954
|
},
|
|
936
955
|
}}
|
|
@@ -944,7 +963,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
944
963
|
_F, _LANG,
|
|
945
964
|
],
|
|
946
965
|
"responses": {
|
|
947
|
-
"200":
|
|
966
|
+
"200": _features_schema(profile.openapi_version if profile else "3.0.0"),
|
|
948
967
|
"400": _ERR_400, "404": _ERR_404, "500": _ERR_500,
|
|
949
968
|
},
|
|
950
969
|
}}
|
|
@@ -970,7 +989,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
970
989
|
"tags": [tag],
|
|
971
990
|
"parameters": [_F],
|
|
972
991
|
"responses": {
|
|
973
|
-
"200":
|
|
992
|
+
"200": _features_schema(profile.openapi_version if profile else "3.0.0"),
|
|
974
993
|
"400": _ERR_400, "500": _ERR_500,
|
|
975
994
|
},
|
|
976
995
|
}}
|
|
@@ -988,7 +1007,7 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
988
1007
|
"operationId": _operation_id("getInstance", coll.id),
|
|
989
1008
|
"tags": [tag],
|
|
990
1009
|
"parameters": [instance_id_param, _F],
|
|
991
|
-
"responses": {"200":
|
|
1010
|
+
"responses": {"200": _features_schema(profile.openapi_version if profile else "3.0.0")},
|
|
992
1011
|
}}
|
|
993
1012
|
# instance-level query sub-paths — GET + optional POST
|
|
994
1013
|
for sub_qt in (active - {"instances"}):
|
|
@@ -1076,8 +1095,9 @@ def _collection_paths(coll: Collection, examples: dict | None = None,
|
|
|
1076
1095
|
|
|
1077
1096
|
|
|
1078
1097
|
def _core_paths(profile: ServiceProfile) -> dict:
|
|
1098
|
+
version = profile.openapi_version
|
|
1079
1099
|
landing_response = _landing_page_schema(
|
|
1080
|
-
profile.req_uri,
|
|
1100
|
+
profile.req_uri, version,
|
|
1081
1101
|
title=profile.title,
|
|
1082
1102
|
description=profile.description or "",
|
|
1083
1103
|
keywords=profile.keywords or None,
|
|
@@ -1086,9 +1106,15 @@ def _core_paths(profile: ServiceProfile) -> dict:
|
|
|
1086
1106
|
# Conformance response with required conformance classes
|
|
1087
1107
|
conformance_response = _R200_CONFORMANCE.copy()
|
|
1088
1108
|
if profile.required_conformance_classes:
|
|
1089
|
-
|
|
1090
|
-
"
|
|
1091
|
-
|
|
1109
|
+
if version == "3.1.0":
|
|
1110
|
+
conformance_response["content"]["application/json"]["schema"]["properties"]["conformsTo"]["contains"] = {
|
|
1111
|
+
"enum": profile.required_conformance_classes
|
|
1112
|
+
}
|
|
1113
|
+
else:
|
|
1114
|
+
conformance_response["content"]["application/json"]["schema"]["properties"]["conformsTo"]["items"] = {
|
|
1115
|
+
"type": "string",
|
|
1116
|
+
"enum": profile.required_conformance_classes
|
|
1117
|
+
}
|
|
1092
1118
|
|
|
1093
1119
|
return {
|
|
1094
1120
|
"/": {"get": {
|
|
@@ -1305,7 +1331,7 @@ def build_openapi(profile: ServiceProfile) -> dict:
|
|
|
1305
1331
|
info["x-default-locale"] = profile.resource_default_locale
|
|
1306
1332
|
|
|
1307
1333
|
return {
|
|
1308
|
-
"openapi":
|
|
1334
|
+
"openapi": profile.openapi_version,
|
|
1309
1335
|
"info": info,
|
|
1310
1336
|
# Per OGC API - EDR Part 3 REQ_publishing: this profile is implementation-independent.
|
|
1311
1337
|
# The placeholder server URL "/" is required by OpenAPI validators but implementations
|
|
@@ -1348,6 +1374,7 @@ def build_openapi(profile: ServiceProfile) -> dict:
|
|
|
1348
1374
|
# ---------------------------------------------------------------------------
|
|
1349
1375
|
|
|
1350
1376
|
def build_asyncapi(profile: ServiceProfile) -> dict:
|
|
1377
|
+
version = profile.openapi_version
|
|
1351
1378
|
if not profile.pubsub:
|
|
1352
1379
|
raise ValueError("profile has no pubsub configuration")
|
|
1353
1380
|
|
|
@@ -1392,7 +1419,7 @@ def build_asyncapi(profile: ServiceProfile) -> dict:
|
|
|
1392
1419
|
"type": "object",
|
|
1393
1420
|
"required": ["type", "properties"],
|
|
1394
1421
|
"properties": {
|
|
1395
|
-
"type": {"type": "string", "
|
|
1422
|
+
"type": {"type": "string", **_const("Feature", version)},
|
|
1396
1423
|
"properties": {
|
|
1397
1424
|
"type": "object",
|
|
1398
1425
|
"required": ["id", "timestamp"],
|
|
@@ -1498,7 +1525,7 @@ def _individual_test_adoc(profile: ServiceProfile, test_id: str) -> str:
|
|
|
1498
1525
|
f"identifier:: /conf/{profile.name}/{test.id}",
|
|
1499
1526
|
f"target:: /req/{profile.name}/{test.requirement_id}",
|
|
1500
1527
|
f"test-purpose:: Validate that {test.id.replace('-', ' ')} is correctly implemented.",
|
|
1501
|
-
"test-method::",
|
|
1528
|
+
f"test-method:: {test.method or ''}",
|
|
1502
1529
|
]
|
|
1503
1530
|
for step in test.steps:
|
|
1504
1531
|
lines.append(f"step:: {step}")
|
|
@@ -1601,7 +1628,7 @@ def _individual_test_adoc_for(profile: ServiceProfile, cls: str, test) -> str:
|
|
|
1601
1628
|
f"identifier:: /conf/{cls}/{test.id}",
|
|
1602
1629
|
f"target:: /req/{cls}/{test.requirement_id}",
|
|
1603
1630
|
f"test-purpose:: Validate that {test.id.replace('-', ' ')} is correctly implemented.",
|
|
1604
|
-
"test-method::",
|
|
1631
|
+
f"test-method:: {test.method or ''}",
|
|
1605
1632
|
]
|
|
1606
1633
|
for step in test.steps:
|
|
1607
1634
|
lines.append(f"step:: {step}")
|
{oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/models.py
RENAMED
|
@@ -261,6 +261,7 @@ class AbstractTest(BaseModel):
|
|
|
261
261
|
id: str # mirrors the requirement id it tests
|
|
262
262
|
requirement_id: str
|
|
263
263
|
steps: list[str] = Field(min_length=1)
|
|
264
|
+
method: str | None = Field(default=None, description="Optional test method description")
|
|
264
265
|
|
|
265
266
|
@model_validator(mode="after")
|
|
266
267
|
def ids_must_match(self) -> AbstractTest:
|
|
@@ -848,6 +849,10 @@ class ServiceProfile(BaseModel):
|
|
|
848
849
|
name: Annotated[str, Field(pattern=r"^[a-z0-9_]+$")]
|
|
849
850
|
title: str
|
|
850
851
|
version: str = "1.0"
|
|
852
|
+
openapi_version: Literal["3.0.0", "3.1.0"] = Field(
|
|
853
|
+
default="3.0.0",
|
|
854
|
+
description="OpenAPI version to generate (3.0.0 for compatibility, 3.1.0 for latest features)"
|
|
855
|
+
)
|
|
851
856
|
description: str | None = Field(
|
|
852
857
|
default=None,
|
|
853
858
|
description=(
|
{oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3/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.6.
|
|
3
|
+
Version: 3.6.3
|
|
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
|
|
@@ -47,7 +47,7 @@ Dynamic: license-file
|
|
|
47
47
|
|
|
48
48
|
# OGC API Service Profile Builder
|
|
49
49
|
|
|
50
|
-
Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.1.0, AsyncAPI, AsciiDoc requirements and conformance tests, and a publication-ready Metanorma PDF with optional custom (e.g. DGIWG) branding.
|
|
50
|
+
Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.0.0/3.1.0, AsyncAPI, AsciiDoc requirements and conformance tests, and a publication-ready Metanorma PDF with optional custom (e.g. DGIWG) branding.
|
|
51
51
|
|
|
52
52
|
[](https://pypi.org/project/oapi-profile-builder/)
|
|
53
53
|
[](LICENSE)
|
|
@@ -57,7 +57,9 @@ Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — O
|
|
|
57
57
|
## Quick Start
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
|
|
60
|
+
# Base install. Adds Pillow (cover image generation) with the [pdf] extra —
|
|
61
|
+
# required whenever your profile sets document_metadata.cover.logo.
|
|
62
|
+
pip install 'oapi-profile-builder[pdf]'
|
|
61
63
|
|
|
62
64
|
# Copy an example config and edit it
|
|
63
65
|
cp examples/minimal_profile.yaml my_profile.yaml
|
|
@@ -70,7 +72,7 @@ That's it. The `output/` directory will contain:
|
|
|
70
72
|
|
|
71
73
|
```
|
|
72
74
|
output/
|
|
73
|
-
├── openapi.yaml # OpenAPI 3.1.0 — ready for Swagger UI, Redoc, schemathesis
|
|
75
|
+
├── openapi.yaml # OpenAPI 3.0.0 or 3.1.0 — ready for Swagger UI, Redoc, schemathesis
|
|
74
76
|
├── profile_config.json # Round-trip serialized profile
|
|
75
77
|
├── document.adoc # Metanorma root document
|
|
76
78
|
├── sections/ # Abstract, Preface, Scope, Conformance, References, Terms
|
|
@@ -186,8 +188,18 @@ OGC API - EDR CITE Results
|
|
|
186
188
|
Anyone with a GitHub account can generate artifacts from their own config without cloning or installing anything:
|
|
187
189
|
|
|
188
190
|
1. Open a new issue using the **"Generate profile artifacts"** template ([New issue](../../issues/new/choose)).
|
|
189
|
-
2. Paste your profile YAML into the config field
|
|
190
|
-
3.
|
|
191
|
+
2. Paste your profile YAML into the config field.
|
|
192
|
+
3. If your profile references local files (typically a cover logo via `document_metadata.cover.logo`), drag-and-drop them into the **"Additional assets (optional)"** field. Reference each uploaded file in your YAML as `uploads/<original-filename>` — for example:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
document_metadata:
|
|
196
|
+
cover:
|
|
197
|
+
logo: uploads/my_logo.png
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The workflow downloads each dropped file into `./uploads/` before generation. It parses your YAML to figure out the target filename, so images that GitHub renders as an `<img>` tag with a UUID URL still land at the exact path your config references. Tip: to skip the upload entirely for the DGIWG logo, point at the copy that already ships in this repo: `logo: examples/assets/DGIWG_logo.png`.
|
|
201
|
+
4. Tick "Compile a PDF?" if you also want a Metanorma PDF, then submit.
|
|
202
|
+
5. A workflow validates the config, generates the artifacts, and comments on the issue with a link to download them as a zip (retained for 14 days).
|
|
191
203
|
|
|
192
204
|
The workflow only runs the generator over your declarative config — it never executes user-supplied code — and comments back on both success and failure. Don't paste secrets: the artifacts are attached to a public workflow run.
|
|
193
205
|
|
|
@@ -366,6 +378,7 @@ abstract_tests: []
|
|
|
366
378
|
| `processes` | list | no | OGC API Processes to expose in the OpenAPI |
|
|
367
379
|
| `requirements` | list | no | Normative requirements for the AsciiDoc/PDF |
|
|
368
380
|
| `abstract_tests` | list | no | Conformance tests — each must reference a valid requirement `id` |
|
|
381
|
+
| `abstract_tests[].method` | string | no | Optional test method description |
|
|
369
382
|
| `pubsub` | object | no | OGC API - EDR Part 2 PubSub config — generates `asyncapi.yaml` |
|
|
370
383
|
| `collection_examples` | object | no | `{collectionId: {instanceId: "..."}}` — used by `validate-server` |
|
|
371
384
|
| `paging` | object | no | Features `/items` paging — adds a validated `limit` query parameter to the generated OpenAPI (see below) |
|
|
@@ -660,6 +673,7 @@ requirements:
|
|
|
660
673
|
abstract_tests:
|
|
661
674
|
- id: position-coveragejson # must equal requirement_id
|
|
662
675
|
requirement_id: position-coveragejson
|
|
676
|
+
method: Manual inspection of the /position response.
|
|
663
677
|
steps:
|
|
664
678
|
- Send GET /collections/{id}/position?coords=POINT(lon lat).
|
|
665
679
|
- Verify Content-Type is application/prs.coverage+json.
|
|
@@ -812,7 +826,9 @@ Two `document_metadata` blocks let you rebrand the PDF (e.g. for DGIWG) without
|
|
|
812
826
|
document_metadata:
|
|
813
827
|
doc_number: "DGIWG 134"
|
|
814
828
|
cover:
|
|
815
|
-
logo: assets/
|
|
829
|
+
logo: examples/assets/DGIWG_logo.png # path relative to the working directory (or absolute).
|
|
830
|
+
# From the self-service issue form use uploads/<file>
|
|
831
|
+
# instead and drag-drop the file into "Additional assets".
|
|
816
832
|
tagline: Delivering Military Advantage through multi-national geospatial interoperability
|
|
817
833
|
background: "#FFFFFF" # optional cover background (default white)
|
|
818
834
|
text_color: "#1F3864" # optional cover text colour (default black)
|
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/src/oapi_profile_builder/cite_features.py
RENAMED
|
File without changes
|
|
File without changes
|
{oapi_profile_builder-3.6.1 → oapi_profile_builder-3.6.3}/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
|