oapi-profile-builder 2.0.2__tar.gz → 2.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.
Files changed (19) hide show
  1. {oapi_profile_builder-2.0.2/src/oapi_profile_builder.egg-info → oapi_profile_builder-2.1.0}/PKG-INFO +168 -9
  2. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/README.md +167 -8
  3. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/pyproject.toml +1 -1
  4. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder/generate.py +292 -20
  5. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder/models.py +115 -0
  6. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0/src/oapi_profile_builder.egg-info}/PKG-INFO +168 -9
  7. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/LICENSE +0 -0
  8. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/setup.cfg +0 -0
  9. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder/__init__.py +0 -0
  10. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder/cite.py +0 -0
  11. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder/cite_features.py +0 -0
  12. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder/cli.py +0 -0
  13. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder/compile.py +0 -0
  14. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder/server_validation.py +0 -0
  15. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
  16. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
  17. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
  18. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
  19. {oapi_profile_builder-2.0.2 → oapi_profile_builder-2.1.0}/src/oapi_profile_builder.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oapi-profile-builder
3
- Version: 2.0.2
3
+ Version: 2.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
@@ -63,6 +63,9 @@ pip install oapi-profile-builder
63
63
 
64
64
  ## Workflow
65
65
 
66
+ <img width="1001" height="721" alt="OGC API Service Profile Builder - Pydantic Validation Architecture drawio" src="https://github.com/user-attachments/assets/092c3dfc-549e-41b0-8a92-af0b89689950" />
67
+
68
+
66
69
  ### 1. Author a Profile Config
67
70
 
68
71
  A profile config is a YAML or JSON file. Start with the minimal example:
@@ -128,7 +131,7 @@ abstract_tests:
128
131
  - Verify each feature contains gauge_height property.
129
132
  ```
130
133
 
131
- See [`examples/minimal_profile.yaml`](examples/minimal_profile.yaml) for a complete working example and [`examples/nwsviz_profile.yaml`](examples/nwsviz_profile.yaml) for a full profile with 13 collections, 3 processes, requirements, abstract tests, and document metadata.
134
+ See [`examples/minimal_profile.yaml`](examples/minimal_profile.yaml) for a complete working example, [`examples/insitu_observations_profile.yaml`](examples/insitu_observations_profile.yaml) for a full in-situ observations profile with rich parameter constraints, CRS listing, temporal extent, and custom dimensions, and [`examples/nwsviz_profile.yaml`](examples/nwsviz_profile.yaml) for a full profile with 13 collections, 3 processes, requirements, abstract tests, and document metadata.
132
135
 
133
136
  ### 2. Generate Profile Artifacts
134
137
 
@@ -293,6 +296,133 @@ The skipped tests are optional features not implemented by the server.
293
296
 
294
297
  ---
295
298
 
299
+ ## Profile Configuration Guide
300
+
301
+ This section explains what is and isn't allowed when creating a profile, and how the tool validates your configuration.
302
+
303
+ ### What Gets Validated
304
+
305
+ When you run `generate` or `validate`, the tool instantiates a `ServiceProfile` Pydantic model that enforces all of the following rules before any files are written. If any rule is violated, you get a clear error message pointing to the offending field.
306
+
307
+ #### Profile-Level Fields
308
+
309
+ | Field | Rules |
310
+ |---|---|
311
+ | `name` | Must match `^[a-z0-9_]+$` — lowercase letters, digits, and underscores only. Used in OGC URIs. |
312
+ | `title` | Any non-empty string. |
313
+ | `version` | Any string. Defaults to `"1.0"`. |
314
+ | `collections` | At least one collection is required. No duplicate `id` values. |
315
+
316
+ #### Collection IDs
317
+
318
+ By default, collection IDs can be any string accepted by edr-pydantic. To enforce a naming convention across all collections, use `collection_id_pattern`:
319
+
320
+ ```yaml
321
+ # Only allow lowercase snake_case collection IDs
322
+ collection_id_pattern: "^[a-z][a-z0-9_]*$"
323
+ ```
324
+
325
+ The pattern is matched using Python's `re.fullmatch()`, so it must match the **entire** ID string.
326
+
327
+ #### CRS, TRS, and VRS Constraints
328
+
329
+ Each collection declares a CRS in `extent.spatial.crs`, and optionally a TRS in `extent.temporal.trs` and VRS in `extent.vertical.vrs`. The profile can constrain these values in two ways:
330
+
331
+ **Enumerated list** — only the exact values listed are accepted:
332
+
333
+ ```yaml
334
+ extent_requirements:
335
+ minimum_bbox: [-180, -90, 180, 90]
336
+ allowed_crs:
337
+ - "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
338
+ - "http://www.opengis.net/def/crs/EPSG/0/4326"
339
+ ```
340
+
341
+ **Regex pattern** — any value matching the pattern is accepted:
342
+
343
+ ```yaml
344
+ extent_requirements:
345
+ minimum_bbox: [-180, -90, 180, 90]
346
+ # Accept any OGC or EPSG CRS
347
+ crs_pattern: "^http://www\\.opengis\\.net/def/crs/(OGC|EPSG)/.*$"
348
+ ```
349
+
350
+ If both `allowed_crs` and `crs_pattern` are specified, a collection's CRS must satisfy **both**. At least one of `allowed_crs` or `crs_pattern` is required when `extent_requirements` is present.
351
+
352
+ The same enum/regex approach works for TRS (`allowed_trs` / `trs_pattern`) and VRS (`allowed_vrs` / `vrs_pattern`).
353
+
354
+ #### Parameter Name Constraints
355
+
356
+ By default, parameter names (the keys in `parameter_names`) can be any string. To enforce a naming convention, use `parameter_name_pattern`:
357
+
358
+ ```yaml
359
+ # CF-style lowercase parameter names
360
+ parameter_name_pattern: "^[a-z][a-z0-9_]*$"
361
+ ```
362
+
363
+ ```yaml
364
+ # Allow uppercase abbreviations like WMO codes
365
+ parameter_name_pattern: "^[A-Za-z][A-Za-z0-9_]*$"
366
+ ```
367
+
368
+ Every key in every collection's `parameter_names` must match this pattern. The pattern uses `re.fullmatch()`.
369
+
370
+ Additionally, per OGC API - EDR Part 3, every parameter must specify both `unit` and `observedProperty`. The tool enforces this automatically.
371
+
372
+ #### Requirement and Test IDs
373
+
374
+ | Field | Rules |
375
+ |---|---|
376
+ | Requirement `id` | Must match `^[a-z0-9][a-z0-9\-]*$` — lowercase, digits, hyphens. Cannot end with a hyphen. |
377
+ | AbstractTest `id` | Must exactly equal its `requirement_id`. |
378
+ | AbstractTest `requirement_id` | Must reference an existing requirement `id`. |
379
+
380
+ #### What Happens When Validation Fails
381
+
382
+ The tool prints a Pydantic validation error with the field path and a human-readable message. For example:
383
+
384
+ ```
385
+ Value error, Collection 'my_data' CRS 'urn:ogc:def:crs:EPSG::4326'
386
+ does not match crs_pattern '^http://www\.opengis\.net/def/crs/(OGC|EPSG)/.*$'
387
+ ```
388
+
389
+ ```
390
+ Value error, Parameter name 'WIND_SPEED' in collection 'weather'
391
+ does not match parameter_name_pattern '^[a-z][a-z0-9_]*$'
392
+ ```
393
+
394
+ ```
395
+ Value error, Collection id 'My-Collection' does not match
396
+ collection_id_pattern '^[a-z][a-z0-9_]*$'
397
+ ```
398
+
399
+ ### How Patterns Flow Into the Generated OpenAPI
400
+
401
+ When you specify `crs_pattern`, `allowed_crs`, or `parameter_name_pattern`, those constraints are embedded in the generated `openapi.yaml` so that runtime validation tools can enforce them:
402
+
403
+ - `crs_pattern` → `pattern` on the CRS string schema in collection responses
404
+ - `allowed_crs` → `enum` on the CRS string schema
405
+ - `trs_pattern` / `allowed_trs` → `pattern` / `enum` on the TRS field in extent.temporal
406
+ - `vrs_pattern` / `allowed_vrs` → `pattern` / `enum` on the VRS field in extent.vertical
407
+ - `parameter_name_pattern` → `propertyNames.pattern` on the `parameter_names` object schema
408
+
409
+ This means schemathesis, CITE tests, and client SDKs can validate server responses against these constraints without needing access to the original profile YAML.
410
+
411
+ ### Quick Reference: Regex Examples
412
+
413
+ | Use Case | Pattern |
414
+ |---|---|
415
+ | Only OGC CRS84 | `^http://www\\.opengis\\.net/def/crs/OGC/1\\.3/CRS84$` |
416
+ | Any OGC or EPSG CRS | `^http://www\\.opengis\\.net/def/crs/(OGC\|EPSG)/.*$` |
417
+ | Any valid CRS URI | `^http://www\\.opengis\\.net/def/crs/.*$` |
418
+ | ISO-8601 TRS family | `^http://www\\.opengis\\.net/def/uom/ISO-8601/.*$` |
419
+ | Lowercase snake_case names | `^[a-z][a-z0-9_]*$` |
420
+ | CF standard name style | `^[a-z][a-z0-9_]*(_[a-z0-9]+)*$` |
421
+ | WMO-style alphanumeric | `^[A-Za-z][A-Za-z0-9_]*$` |
422
+ | Lowercase with hyphens | `^[a-z][a-z0-9\\-]*$` |
423
+
424
+ ---
425
+
296
426
  ## Config Reference
297
427
 
298
428
  ### Top-level fields
@@ -313,7 +443,8 @@ The skipped tests are optional features not implemented by the server.
313
443
  | `required_conformance_classes` | `list[string]` | no | Conformance classes that implementations must declare. Defaults to EDR Core |
314
444
  | `extent_requirements` | `object` | no | Profile-level extent restrictions (see below) |
315
445
  | `output_formats` | `list` | no | Profile-level output format definitions with schema references (see below) |
316
- | `collection_id_pattern` | `string` | no | Regex pattern for valid collection IDs |
446
+ | `collection_id_pattern` | `string` | no | Regex pattern that all collection IDs must match (validated at build time) |
447
+ | `parameter_name_pattern` | `string` | no | Regex pattern that all `parameter_names` keys must match (validated at build time) |
317
448
 
318
449
  ---
319
450
 
@@ -376,7 +507,7 @@ parameter_names:
376
507
 
377
508
  ### `extent_requirements`
378
509
 
379
- Profile-level extent restrictions per OGC API - EDR Part 3 REQ_extent.
510
+ Profile-level extent restrictions per OGC API - EDR Part 3 REQ_extent. These constraints are **enforced at profile build time** — if any collection's CRS, TRS, or VRS value violates the rules here, the profile will be rejected with a clear error message. The constraints are also **embedded in the generated OpenAPI** so that downstream tools (schemathesis, CITE tests, client SDKs) can enforce them at runtime.
380
511
 
381
512
  | Field | Type | Required | Description |
382
513
  |---|---|---|---|
@@ -390,6 +521,8 @@ Profile-level extent restrictions per OGC API - EDR Part 3 REQ_extent.
390
521
 
391
522
  **Note:** Either `allowed_crs` or `crs_pattern` must be specified.
392
523
 
524
+ **Enum approach** — lock down to specific values:
525
+
393
526
  ```yaml
394
527
  extent_requirements:
395
528
  minimum_bbox: [-180, -90, 180, 90]
@@ -400,6 +533,19 @@ extent_requirements:
400
533
  - "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
401
534
  ```
402
535
 
536
+ **Regex approach** — allow any CRS from a family:
537
+
538
+ ```yaml
539
+ extent_requirements:
540
+ minimum_bbox: [-180, -90, 180, 90]
541
+ # Accept any OGC or EPSG CRS
542
+ crs_pattern: "^http://www\\.opengis\\.net/def/crs/(OGC|EPSG)/.*$"
543
+ # Accept any ISO-8601 TRS
544
+ trs_pattern: "^http://www\\.opengis\\.net/def/uom/ISO-8601/.*$"
545
+ ```
546
+
547
+ Both approaches can coexist — if both `allowed_crs` and `crs_pattern` are specified, a collection's CRS must satisfy **both** constraints.
548
+
403
549
  ---
404
550
 
405
551
  ### `output_formats[]`
@@ -546,6 +692,7 @@ This tool implements the requirements of OGC API - EDR Part 3: Service Profiles
546
692
  ### Key Compliance Features
547
693
 
548
694
  1. **Profile OpenAPI Document** (REQ_publishing)
695
+ - Generated OpenAPI version is **3.1.0** (required by OGC API - EDR Part 3)
549
696
  - Generated OpenAPI has empty `servers` array (profile is implementation-independent)
550
697
  - Landing page schema requires `profile` link relation
551
698
  - Profile URI advertised in `x-ogc-profile` info field
@@ -561,12 +708,23 @@ This tool implements the requirements of OGC API - EDR Part 3: Service Profiles
561
708
  4. **Extent Requirements** (REQ_extent)
562
709
  - Profile-level `extent_requirements` specify minimum bounds
563
710
  - CRS/TRS/VRS restrictions via enumerated lists or regex patterns
711
+ - **Enforced at build time**: collection CRS/TRS/VRS values are validated against `allowed_*` lists and `*_pattern` regexes
712
+ - **Propagated to OpenAPI**: constraints appear as `enum` or `pattern` in the generated collection response schemas
713
+
714
+ 5. **Parameter Names** (REQ_parameter-names)
715
+ - Validates that all parameters specify `unit` and `observedProperty`
716
+ - Optional `parameter_name_pattern` enforces naming conventions across all collections
717
+ - Pattern constraints are embedded in the generated OpenAPI as `propertyNames.pattern`
718
+
719
+ 6. **Collection ID Pattern**
720
+ - Optional `collection_id_pattern` enforces naming conventions for collection IDs
721
+ - Validated at build time via `re.fullmatch()`
564
722
 
565
- 5. **Output Formats** (REQ_output-format)
723
+ 7. **Output Formats** (REQ_output-format)
566
724
  - Profile-level `output_formats` with schema references
567
725
  - Links to JSON Schema, XML Schema, or format specifications
568
726
 
569
- 6. **Pub/Sub** (REQ_pubsub)
727
+ 8. **Pub/Sub** (REQ_pubsub)
570
728
  - Automatically adds Part 2 conformance requirement when `pubsub` is present
571
729
  - AsyncAPI document specifies channels and payloads
572
730
 
@@ -602,8 +760,9 @@ generate(profile, Path("./output"))
602
760
  │ ├── cite.py # OGC CITE test suite orchestration
603
761
  │ └── cli.py # CLI entry point
604
762
  ├── examples/
605
- │ ├── water_gauge.yaml # Minimal example profile config
606
- └── nwsviz_profile.yaml # Full NWSViz profile: 13 collections, 3 processes, PDF metadata
763
+ │ ├── minimal_profile.yaml # Minimal working profile
764
+ ├── insitu_observations_profile.yaml # Full in-situ profile: parameters, CRS, temporal, custom dims
765
+ │ └── nwsviz_profile.yaml # Full NWSViz profile: 13 collections, 3 processes, PDF metadata
607
766
  ├── profile.schema.json # Machine-readable JSON Schema for profile configs
608
767
  └── pyproject.toml
609
768
  ```
@@ -619,7 +778,7 @@ generate(profile, Path("./output"))
619
778
 
620
779
  ## License
621
780
 
622
- MIT — See [LICENSE](LICENSE) for details.
781
+ Apache — See [LICENSE](LICENSE) for details.
623
782
 
624
783
  ## Contact
625
784
 
@@ -18,6 +18,9 @@ pip install oapi-profile-builder
18
18
 
19
19
  ## Workflow
20
20
 
21
+ <img width="1001" height="721" alt="OGC API Service Profile Builder - Pydantic Validation Architecture drawio" src="https://github.com/user-attachments/assets/092c3dfc-549e-41b0-8a92-af0b89689950" />
22
+
23
+
21
24
  ### 1. Author a Profile Config
22
25
 
23
26
  A profile config is a YAML or JSON file. Start with the minimal example:
@@ -83,7 +86,7 @@ abstract_tests:
83
86
  - Verify each feature contains gauge_height property.
84
87
  ```
85
88
 
86
- See [`examples/minimal_profile.yaml`](examples/minimal_profile.yaml) for a complete working example and [`examples/nwsviz_profile.yaml`](examples/nwsviz_profile.yaml) for a full profile with 13 collections, 3 processes, requirements, abstract tests, and document metadata.
89
+ See [`examples/minimal_profile.yaml`](examples/minimal_profile.yaml) for a complete working example, [`examples/insitu_observations_profile.yaml`](examples/insitu_observations_profile.yaml) for a full in-situ observations profile with rich parameter constraints, CRS listing, temporal extent, and custom dimensions, and [`examples/nwsviz_profile.yaml`](examples/nwsviz_profile.yaml) for a full profile with 13 collections, 3 processes, requirements, abstract tests, and document metadata.
87
90
 
88
91
  ### 2. Generate Profile Artifacts
89
92
 
@@ -248,6 +251,133 @@ The skipped tests are optional features not implemented by the server.
248
251
 
249
252
  ---
250
253
 
254
+ ## Profile Configuration Guide
255
+
256
+ This section explains what is and isn't allowed when creating a profile, and how the tool validates your configuration.
257
+
258
+ ### What Gets Validated
259
+
260
+ When you run `generate` or `validate`, the tool instantiates a `ServiceProfile` Pydantic model that enforces all of the following rules before any files are written. If any rule is violated, you get a clear error message pointing to the offending field.
261
+
262
+ #### Profile-Level Fields
263
+
264
+ | Field | Rules |
265
+ |---|---|
266
+ | `name` | Must match `^[a-z0-9_]+$` — lowercase letters, digits, and underscores only. Used in OGC URIs. |
267
+ | `title` | Any non-empty string. |
268
+ | `version` | Any string. Defaults to `"1.0"`. |
269
+ | `collections` | At least one collection is required. No duplicate `id` values. |
270
+
271
+ #### Collection IDs
272
+
273
+ By default, collection IDs can be any string accepted by edr-pydantic. To enforce a naming convention across all collections, use `collection_id_pattern`:
274
+
275
+ ```yaml
276
+ # Only allow lowercase snake_case collection IDs
277
+ collection_id_pattern: "^[a-z][a-z0-9_]*$"
278
+ ```
279
+
280
+ The pattern is matched using Python's `re.fullmatch()`, so it must match the **entire** ID string.
281
+
282
+ #### CRS, TRS, and VRS Constraints
283
+
284
+ Each collection declares a CRS in `extent.spatial.crs`, and optionally a TRS in `extent.temporal.trs` and VRS in `extent.vertical.vrs`. The profile can constrain these values in two ways:
285
+
286
+ **Enumerated list** — only the exact values listed are accepted:
287
+
288
+ ```yaml
289
+ extent_requirements:
290
+ minimum_bbox: [-180, -90, 180, 90]
291
+ allowed_crs:
292
+ - "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
293
+ - "http://www.opengis.net/def/crs/EPSG/0/4326"
294
+ ```
295
+
296
+ **Regex pattern** — any value matching the pattern is accepted:
297
+
298
+ ```yaml
299
+ extent_requirements:
300
+ minimum_bbox: [-180, -90, 180, 90]
301
+ # Accept any OGC or EPSG CRS
302
+ crs_pattern: "^http://www\\.opengis\\.net/def/crs/(OGC|EPSG)/.*$"
303
+ ```
304
+
305
+ If both `allowed_crs` and `crs_pattern` are specified, a collection's CRS must satisfy **both**. At least one of `allowed_crs` or `crs_pattern` is required when `extent_requirements` is present.
306
+
307
+ The same enum/regex approach works for TRS (`allowed_trs` / `trs_pattern`) and VRS (`allowed_vrs` / `vrs_pattern`).
308
+
309
+ #### Parameter Name Constraints
310
+
311
+ By default, parameter names (the keys in `parameter_names`) can be any string. To enforce a naming convention, use `parameter_name_pattern`:
312
+
313
+ ```yaml
314
+ # CF-style lowercase parameter names
315
+ parameter_name_pattern: "^[a-z][a-z0-9_]*$"
316
+ ```
317
+
318
+ ```yaml
319
+ # Allow uppercase abbreviations like WMO codes
320
+ parameter_name_pattern: "^[A-Za-z][A-Za-z0-9_]*$"
321
+ ```
322
+
323
+ Every key in every collection's `parameter_names` must match this pattern. The pattern uses `re.fullmatch()`.
324
+
325
+ Additionally, per OGC API - EDR Part 3, every parameter must specify both `unit` and `observedProperty`. The tool enforces this automatically.
326
+
327
+ #### Requirement and Test IDs
328
+
329
+ | Field | Rules |
330
+ |---|---|
331
+ | Requirement `id` | Must match `^[a-z0-9][a-z0-9\-]*$` — lowercase, digits, hyphens. Cannot end with a hyphen. |
332
+ | AbstractTest `id` | Must exactly equal its `requirement_id`. |
333
+ | AbstractTest `requirement_id` | Must reference an existing requirement `id`. |
334
+
335
+ #### What Happens When Validation Fails
336
+
337
+ The tool prints a Pydantic validation error with the field path and a human-readable message. For example:
338
+
339
+ ```
340
+ Value error, Collection 'my_data' CRS 'urn:ogc:def:crs:EPSG::4326'
341
+ does not match crs_pattern '^http://www\.opengis\.net/def/crs/(OGC|EPSG)/.*$'
342
+ ```
343
+
344
+ ```
345
+ Value error, Parameter name 'WIND_SPEED' in collection 'weather'
346
+ does not match parameter_name_pattern '^[a-z][a-z0-9_]*$'
347
+ ```
348
+
349
+ ```
350
+ Value error, Collection id 'My-Collection' does not match
351
+ collection_id_pattern '^[a-z][a-z0-9_]*$'
352
+ ```
353
+
354
+ ### How Patterns Flow Into the Generated OpenAPI
355
+
356
+ When you specify `crs_pattern`, `allowed_crs`, or `parameter_name_pattern`, those constraints are embedded in the generated `openapi.yaml` so that runtime validation tools can enforce them:
357
+
358
+ - `crs_pattern` → `pattern` on the CRS string schema in collection responses
359
+ - `allowed_crs` → `enum` on the CRS string schema
360
+ - `trs_pattern` / `allowed_trs` → `pattern` / `enum` on the TRS field in extent.temporal
361
+ - `vrs_pattern` / `allowed_vrs` → `pattern` / `enum` on the VRS field in extent.vertical
362
+ - `parameter_name_pattern` → `propertyNames.pattern` on the `parameter_names` object schema
363
+
364
+ This means schemathesis, CITE tests, and client SDKs can validate server responses against these constraints without needing access to the original profile YAML.
365
+
366
+ ### Quick Reference: Regex Examples
367
+
368
+ | Use Case | Pattern |
369
+ |---|---|
370
+ | Only OGC CRS84 | `^http://www\\.opengis\\.net/def/crs/OGC/1\\.3/CRS84$` |
371
+ | Any OGC or EPSG CRS | `^http://www\\.opengis\\.net/def/crs/(OGC\|EPSG)/.*$` |
372
+ | Any valid CRS URI | `^http://www\\.opengis\\.net/def/crs/.*$` |
373
+ | ISO-8601 TRS family | `^http://www\\.opengis\\.net/def/uom/ISO-8601/.*$` |
374
+ | Lowercase snake_case names | `^[a-z][a-z0-9_]*$` |
375
+ | CF standard name style | `^[a-z][a-z0-9_]*(_[a-z0-9]+)*$` |
376
+ | WMO-style alphanumeric | `^[A-Za-z][A-Za-z0-9_]*$` |
377
+ | Lowercase with hyphens | `^[a-z][a-z0-9\\-]*$` |
378
+
379
+ ---
380
+
251
381
  ## Config Reference
252
382
 
253
383
  ### Top-level fields
@@ -268,7 +398,8 @@ The skipped tests are optional features not implemented by the server.
268
398
  | `required_conformance_classes` | `list[string]` | no | Conformance classes that implementations must declare. Defaults to EDR Core |
269
399
  | `extent_requirements` | `object` | no | Profile-level extent restrictions (see below) |
270
400
  | `output_formats` | `list` | no | Profile-level output format definitions with schema references (see below) |
271
- | `collection_id_pattern` | `string` | no | Regex pattern for valid collection IDs |
401
+ | `collection_id_pattern` | `string` | no | Regex pattern that all collection IDs must match (validated at build time) |
402
+ | `parameter_name_pattern` | `string` | no | Regex pattern that all `parameter_names` keys must match (validated at build time) |
272
403
 
273
404
  ---
274
405
 
@@ -331,7 +462,7 @@ parameter_names:
331
462
 
332
463
  ### `extent_requirements`
333
464
 
334
- Profile-level extent restrictions per OGC API - EDR Part 3 REQ_extent.
465
+ Profile-level extent restrictions per OGC API - EDR Part 3 REQ_extent. These constraints are **enforced at profile build time** — if any collection's CRS, TRS, or VRS value violates the rules here, the profile will be rejected with a clear error message. The constraints are also **embedded in the generated OpenAPI** so that downstream tools (schemathesis, CITE tests, client SDKs) can enforce them at runtime.
335
466
 
336
467
  | Field | Type | Required | Description |
337
468
  |---|---|---|---|
@@ -345,6 +476,8 @@ Profile-level extent restrictions per OGC API - EDR Part 3 REQ_extent.
345
476
 
346
477
  **Note:** Either `allowed_crs` or `crs_pattern` must be specified.
347
478
 
479
+ **Enum approach** — lock down to specific values:
480
+
348
481
  ```yaml
349
482
  extent_requirements:
350
483
  minimum_bbox: [-180, -90, 180, 90]
@@ -355,6 +488,19 @@ extent_requirements:
355
488
  - "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
356
489
  ```
357
490
 
491
+ **Regex approach** — allow any CRS from a family:
492
+
493
+ ```yaml
494
+ extent_requirements:
495
+ minimum_bbox: [-180, -90, 180, 90]
496
+ # Accept any OGC or EPSG CRS
497
+ crs_pattern: "^http://www\\.opengis\\.net/def/crs/(OGC|EPSG)/.*$"
498
+ # Accept any ISO-8601 TRS
499
+ trs_pattern: "^http://www\\.opengis\\.net/def/uom/ISO-8601/.*$"
500
+ ```
501
+
502
+ Both approaches can coexist — if both `allowed_crs` and `crs_pattern` are specified, a collection's CRS must satisfy **both** constraints.
503
+
358
504
  ---
359
505
 
360
506
  ### `output_formats[]`
@@ -501,6 +647,7 @@ This tool implements the requirements of OGC API - EDR Part 3: Service Profiles
501
647
  ### Key Compliance Features
502
648
 
503
649
  1. **Profile OpenAPI Document** (REQ_publishing)
650
+ - Generated OpenAPI version is **3.1.0** (required by OGC API - EDR Part 3)
504
651
  - Generated OpenAPI has empty `servers` array (profile is implementation-independent)
505
652
  - Landing page schema requires `profile` link relation
506
653
  - Profile URI advertised in `x-ogc-profile` info field
@@ -516,12 +663,23 @@ This tool implements the requirements of OGC API - EDR Part 3: Service Profiles
516
663
  4. **Extent Requirements** (REQ_extent)
517
664
  - Profile-level `extent_requirements` specify minimum bounds
518
665
  - CRS/TRS/VRS restrictions via enumerated lists or regex patterns
666
+ - **Enforced at build time**: collection CRS/TRS/VRS values are validated against `allowed_*` lists and `*_pattern` regexes
667
+ - **Propagated to OpenAPI**: constraints appear as `enum` or `pattern` in the generated collection response schemas
668
+
669
+ 5. **Parameter Names** (REQ_parameter-names)
670
+ - Validates that all parameters specify `unit` and `observedProperty`
671
+ - Optional `parameter_name_pattern` enforces naming conventions across all collections
672
+ - Pattern constraints are embedded in the generated OpenAPI as `propertyNames.pattern`
673
+
674
+ 6. **Collection ID Pattern**
675
+ - Optional `collection_id_pattern` enforces naming conventions for collection IDs
676
+ - Validated at build time via `re.fullmatch()`
519
677
 
520
- 5. **Output Formats** (REQ_output-format)
678
+ 7. **Output Formats** (REQ_output-format)
521
679
  - Profile-level `output_formats` with schema references
522
680
  - Links to JSON Schema, XML Schema, or format specifications
523
681
 
524
- 6. **Pub/Sub** (REQ_pubsub)
682
+ 8. **Pub/Sub** (REQ_pubsub)
525
683
  - Automatically adds Part 2 conformance requirement when `pubsub` is present
526
684
  - AsyncAPI document specifies channels and payloads
527
685
 
@@ -557,8 +715,9 @@ generate(profile, Path("./output"))
557
715
  │ ├── cite.py # OGC CITE test suite orchestration
558
716
  │ └── cli.py # CLI entry point
559
717
  ├── examples/
560
- │ ├── water_gauge.yaml # Minimal example profile config
561
- └── nwsviz_profile.yaml # Full NWSViz profile: 13 collections, 3 processes, PDF metadata
718
+ │ ├── minimal_profile.yaml # Minimal working profile
719
+ ├── insitu_observations_profile.yaml # Full in-situ profile: parameters, CRS, temporal, custom dims
720
+ │ └── nwsviz_profile.yaml # Full NWSViz profile: 13 collections, 3 processes, PDF metadata
562
721
  ├── profile.schema.json # Machine-readable JSON Schema for profile configs
563
722
  └── pyproject.toml
564
723
  ```
@@ -574,7 +733,7 @@ generate(profile, Path("./output"))
574
733
 
575
734
  ## License
576
735
 
577
- MIT — See [LICENSE](LICENSE) for details.
736
+ Apache — See [LICENSE](LICENSE) for details.
578
737
 
579
738
  ## Contact
580
739
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "oapi-profile-builder"
7
- version = "2.0.2"
7
+ version = "2.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" }