Skip to main content

Authoritative tooling for creating OGC API Service Profiles (EDR, Features)

Project description

OGC API Service Profile Builder

Generate OGC API - EDR Part 3 Service Profile artifacts from a YAML config — OpenAPI 3.1.0, AsyncAPI, AsciiDoc requirements, and conformance tests.

PyPI License


Quick Start

pip install oapi-profile-builder

# Copy an example config and edit it
cp examples/minimal_profile.yaml my_profile.yaml

# Validate and generate artifacts
oapi-profile-builder generate --config my_profile.yaml --output ./output

That's it. The output/ directory will contain:

output/
├── openapi.yaml          # OpenAPI 3.1.0 — ready for Swagger UI, Redoc, schemathesis
├── profile_config.json   # Round-trip serialized profile
├── document.adoc         # Metanorma root document
├── sections/             # Abstract, Preface, Scope, Conformance, References, Terms
├── requirements/         # Individual REQ_*.adoc files
└── abstract_tests/       # Individual ATS_*.adoc files

Example Profiles

Three working examples are included:

File What it shows
examples/minimal_profile.yaml Smallest valid profile — one collection, one requirement
examples/insitu_observations_profile.yaml Full meteorological profile — 8 parameters with QUDT units, CF standard names, metocean extensions, CRS listing, temporal extent, custom dimensions, parameter_schema
examples/nwsviz_profile.yaml Production profile — 13 collections, 3 OGC API Processes, PDF metadata

CLI Reference

oapi-profile-builder generate   --config <file> --output <dir> [--pdf]
oapi-profile-builder validate   --config <file>
oapi-profile-builder validate-server --config <file> --url <url> [--max-examples N] [--stateful]
oapi-profile-builder cite-test  --url <url> [--report <dir>]
oapi-profile-builder cite-test-features --url <url> [--report <dir>]
oapi-profile-builder schema     [--output <file>]

generate

Validates the profile config and writes all artifacts to the output directory.

oapi-profile-builder generate --config my_profile.yaml --output ./output

Add --pdf to also compile an OGC-compliant PDF via the metanorma/metanorma Docker image (Docker required):

oapi-profile-builder generate --config my_profile.yaml --output ./output --pdf

validate

Validates the config without writing any files. Useful in CI before generating.

oapi-profile-builder validate --config my_profile.yaml
# Profile 'my_profile' is valid.

validate-server

Runs schemathesis against a live server using the profile's generated OpenAPI. Requires pip install oapi-profile-builder[validate].

oapi-profile-builder validate-server \
  --config my_profile.yaml \
  --url https://my-server.example.com \
  --max-examples 5

Supply real instanceId values in your config so schemathesis can exercise instance-level paths:

collection_examples:
  my_collection:
    instanceId: "2025-04-02T00:00:00Z"

cite-test / cite-test-features

Runs the official OGC CITE conformance test suites against a live server. Docker and Maven are required for cite-test (EDR); Docker only for cite-test-features.

# OGC API - EDR Part 1 (builds ets-ogcapi-edr10 on first run, ~2 min)
oapi-profile-builder cite-test \
  --url https://my-server.example.com \
  --report ./cite_results

# OGC API - Features Part 1 (pulls pre-built image from Docker Hub)
oapi-profile-builder cite-test-features \
  --url https://my-server.example.com \
  --report ./cite_features_results

Results:

OGC API - EDR CITE Results
  Passed:  76/84
  Failed:  0
  Skipped: 8

✓ All CITE tests passed.

GitHub Actions

No local install needed. Add this to any workflow to generate profile artifacts from a config file:

name: Generate Profile

on:
  push:
    paths: ['my_profile.yaml']

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Generate profile artifacts
        uses: ShaneMill1/OGC-API-Service-Profile-Builder@main
        with:
          config: my_profile.yaml
          output: ./profile_output

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: profile-artifacts
          path: ./profile_output/

To download the artifacts: Actions tab → click the run → scroll to Artifacts at the bottom → download the zip.

Action inputs

Input Default Description
config Path to the profile config YAML (required)
output ./profile_output Output directory
version latest Package version to install
pdf false Compile PDF via Metanorma (Docker required on runner)
cite-url Run OGC CITE tests against this server URL
cite-type edr edr, features, or both

CITE + VPN: The CITE test runner needs to reach the server from GitHub's runners. Servers behind a VPN require a self-hosted runner.


Profile Config Reference

A profile config is a YAML file. The full JSON Schema is at profile.schema.json.

Minimal valid config

name: my_profile          # lowercase, a-z 0-9 _ only
title: My EDR Profile
version: "1.0"
description: Brief description of what this service profile provides.
keywords:
  - my-parameter
  - my-domain

required_conformance_classes:
  - "http://www.opengis.net/spec/ogcapi-edr-1/1.0/conf/core"

extent_requirements:
  minimum_bbox: [-180, -90, 180, 90]
  allowed_crs:
    - "http://www.opengis.net/def/crs/OGC/1.3/CRS84"

output_formats:
  - name: GeoJSON
    media_type: application/geo+json

collections:
  - id: my_collection
    links:
      - href: https://example.com/collections/my_collection
        rel: self
        type: application/json
    extent:
      spatial:
        bbox: [[-180, -90, 180, 90]]
        crs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
    parameter_names:
      temperature:
        type: Parameter
        observedProperty:
          label: Air Temperature
        unit:
          label: Celsius
          symbol: "°C"

Top-level fields

Field Type Required Description
name string yes Lowercase a-z 0-9 _. Drives OGC URIs and OpenAPI operationIds
title string yes Human-readable profile title
version string no Defaults to "1.0"
description string no Human-readable description of the service profile. Surfaces in the OpenAPI info.description and the landing page response schema
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
server_url string no Documentation only — not written to the profile OpenAPI
allow_post_queries bool no When true, generates POST alongside GET for all EDR data query endpoints. Defaults to false
collections list yes One or more EDR collections (see below)
required_conformance_classes list no Conformance classes implementations must declare. Defaults to EDR Core
extent_requirements object no Profile-level CRS/TRS/VRS constraints (see below)
output_formats list no Format name → media type + schema ref mappings
collection_id_pattern string no Regex all collection IDs must match
parameter_name_pattern string no Regex all parameter_names keys must match
parameter_schema object no JSON Schema for parameter objects — replaces the default schema in the generated OpenAPI (see below)
processes list no OGC API Processes to expose in the OpenAPI
requirements list no Normative requirements for the AsciiDoc/PDF
abstract_tests list no Conformance tests — each must reference a valid requirement id
pubsub object no OGC API - EDR Part 2 PubSub config — generates asyncapi.yaml
collection_examples object no {collectionId: {instanceId: "..."}} — used by validate-server
document_metadata object no Metanorma PDF header (doc number, editors, orgs, keywords)

collections[]

Uses the edr-pydantic Collection model — the same schema an EDR server returns at /collections/{id}.

Field Required Description
id yes Collection identifier
title no Human-readable name
description no Longer description
links yes At minimum a self link
extent.spatial.bbox yes [[minLon, minLat, maxLon, maxLat]]
extent.spatial.crs yes CRS URI — validated against extent_requirements
extent.temporal no interval, values, trs
extent.vertical no interval, values, vrs
extent.custom no Custom dimensions (e.g. standard_name, level)
crs no Full list of CRS values this collection supports
output_formats no Format names this collection supports (e.g. [CoverageJSON, GeoJSON])
data_queries no EDR query types: position, area, radius, cube, trajectory, corridor, locations, items, instances
parameter_names no Map of parameter id → Parameter object. All parameters must have unit and observedProperty

data_queries example

data_queries:
  position:
    link:
      href: https://example.com/collections/obs/position
      rel: data
      variables:
        query_type: position
        output_formats: [CoverageJSON]
  radius:
    link:
      href: https://example.com/collections/obs/radius
      rel: data
      variables:
        query_type: radius
        output_formats: [CoverageJSON]
        within_units: [m, km]
        # crs_details: per-query CRS support, validated against extent_requirements
        crs_details:
          - crs: "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
          - crs: "http://www.opengis.net/def/crs/EPSG/0/4326"

parameter_names example

parameter_names:
  air-temperature-2m:
    type: Parameter
    label: Air Temperature at 2m
    description: Instantaneous air temperature at 2 metres above ground
    observedProperty:
      id: "https://vocab.nerc.ac.uk/standard_name/air_temperature"
      label: Air Temperature
    unit:
      label: Kelvin
      symbol:
        value: K
        type: "https://qudt.org/vocab/unit/K"
    measurementType:
      method: point
      duration: PT0S

extent_requirements

Constrains CRS, TRS, and VRS values across all collections. Validated at build time and embedded in the generated OpenAPI.

extent_requirements:
  minimum_bbox: [-180, -90, 180, 90]

  # Option A: exact list
  allowed_crs:
    - "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
    - "http://www.opengis.net/def/crs/EPSG/0/4326"

  # Option B: regex (accepts any OGC or EPSG CRS)
  # crs_pattern: "^http://www\\.opengis\\.net/def/crs/(OGC|EPSG)/.*$"

  allowed_trs:
    - "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"

Either allowed_crs or crs_pattern is required. The same enum/regex approach applies to allowed_trs/trs_pattern and allowed_vrs/vrs_pattern.


parameter_schema

A JSON Schema fragment that replaces the default parameter schema in the generated OpenAPI. Use this to enforce field-level constraints — required fields, QUDT unit URIs, CF standard name URIs, ISO 8601 durations, and custom extension properties.

parameter_schema:
  type: object
  required:
    - type
    - observedProperty
    - measurementType
    - label
    - description
    - unit
    - "metocean:standard_name"
    - "metocean:level"
  properties:
    unit:
      type: object
      properties:
        symbol:
          type: object
          properties:
            type:
              type: string
              pattern: "^https://qudt\\.org/vocab/unit/.*$"
    observedProperty:
      type: object
      properties:
        id:
          type: string
          pattern: "^https://vocab\\.nerc\\.ac\\.uk/standard_name/.*$"
    measurementType:
      type: object
      properties:
        duration:
          type: string
          pattern: "^P(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$"
    "metocean:standard_name":
      type: string
    "metocean:level":
      type: number
  additionalProperties: true

See examples/insitu_observations_profile.yaml for a complete working example.


requirements[] and abstract_tests[]

Requirements drive the AsciiDoc/PDF output. Abstract tests must reference a valid requirement id.

requirements:
  - id: position-coveragejson          # lowercase, hyphens only
    statement: The position query SHALL return CoverageJSON.
    parts:
      - The service SHALL provide a /collections/{id}/position endpoint.
      - The response Content-Type SHALL be application/prs.coverage+json.

abstract_tests:
  - id: position-coveragejson          # must equal requirement_id
    requirement_id: position-coveragejson
    steps:
      - Send GET /collections/{id}/position?coords=POINT(lon lat).
      - Verify Content-Type is application/prs.coverage+json.

processes[]

Adds OGC API Processes paths to the generated OpenAPI.

processes:
  - id: compute-difference
    title: Compute Dataset Difference
    description: Calculates the difference between two datasets.
    output_content:
      application/zip:
        schema:
          type: object

Generates: /processes/compute-difference, /processes/compute-difference/execution, /jobs, /jobs/{jobId}, /jobs/{jobId}/results.


pubsub

When present, generates asyncapi.yaml alongside openapi.yaml.

pubsub:
  broker_host: my-broker.example.com
  broker_port: 5672
  protocol: amqp          # amqp | mqtt | kafka
  collections:
    - my_collection
  filters:
    - name: station
      description: Filter by station ID
      type: string

document_metadata

Required only when compiling a PDF with --pdf.

document_metadata:
  doc_number: "25-myprofile"
  doc_subtype: implementation   # implementation | best-practice | engineering-report
  editors:
    - Jane Smith
  submitting_orgs:
    - My Organization
  keywords:
    - ogcdoc
    - OGC API
    - EDR
  copyright_year: 2026
  external_id: http://www.opengis.net/doc/dp/my-profile/1.0

Validation Rules

The tool enforces these rules at build time. Violations produce clear error messages.

Rule Detail
name format Must match ^[a-z0-9_]+$
No duplicate collection IDs Across the whole profile
extent_requirements requires CRS spec Either allowed_crs or crs_pattern must be set
Collection CRS validated Each extent.spatial.crs checked against allowed_crs/crs_pattern
Collection TRS validated Each extent.temporal.trs checked against allowed_trs/trs_pattern
crs_details validated Each data_queries.*.variables.crs_details[].crs checked against CRS constraints
Parameters need unit + observedProperty Required by OGC API - EDR Part 3
parameter_name_pattern enforced All parameter_names keys must match if set
collection_id_pattern enforced All collection IDs must match if set
Abstract test IDs match requirements requirement_id must reference an existing requirement
Requirement IDs Must match ^[a-z0-9][a-z0-9\-]*$, no trailing hyphen

Programmatic Use

from oapi_profile_builder.models import ServiceProfile
from oapi_profile_builder.generate import generate
from pathlib import Path
import yaml

with open("my_profile.yaml") as f:
    config = yaml.safe_load(f)

profile = ServiceProfile.model_validate(config)  # validates everything
generate(profile, Path("./output"))

Standards

License

Apache 2.0 — see LICENSE.

Contact

Shane Mill · NOAA/NWS/MDL · shane.mill@noaa.gov
Issues: https://github.com/ShaneMill1/OGC-API-Service-Profile-Builder/issues

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oapi_profile_builder-2.5.0.tar.gz (39.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oapi_profile_builder-2.5.0-py3-none-any.whl (43.7 kB view details)

Uploaded Python 3

File details

Details for the file oapi_profile_builder-2.5.0.tar.gz.

File metadata

  • Download URL: oapi_profile_builder-2.5.0.tar.gz
  • Upload date:
  • Size: 39.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.8

File hashes

Hashes for oapi_profile_builder-2.5.0.tar.gz
Algorithm Hash digest
SHA256 a819980ff134a79279aae4bc097024fca6076c20661c16c071f348252b60dba9
MD5 f52d63f1b60567afb2ee30b936d035d5
BLAKE2b-256 01d7aa983dcc92dcbb50f9da9cb762495901a4d24a0a8980c52b7fc0e9db8464

See more details on using hashes here.

File details

Details for the file oapi_profile_builder-2.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for oapi_profile_builder-2.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 45444bac2888176774fba1b40b4a57f160e96d66062c394091343beecccd9216
MD5 f3ebc1abf3a4db89abe0696d813faa1d
BLAKE2b-256 2a7efec4a5353b80b4be798d0b3b6fa9d3bc1d354a7d96e91df5867eac726f4a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page