ogcapi-registry 0.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.
- ogcapi_registry-0.1.0/PKG-INFO +280 -0
- ogcapi_registry-0.1.0/README.md +267 -0
- ogcapi_registry-0.1.0/pyproject.toml +45 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/__init__.py +162 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/client.py +324 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/exceptions.py +75 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/models.py +254 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/ogc_registry.py +475 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/ogc_types.py +463 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/protocols.py +317 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/py.typed +0 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/registry.py +346 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategies/__init__.py +36 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategies/base.py +427 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategies/common.py +194 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategies/features.py +332 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategies/other.py +336 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategies/processes.py +311 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategies/records.py +120 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategies/tiles.py +238 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/strategy_registry.py +523 -0
- ogcapi_registry-0.1.0/src/ogcapi_registry/validator.py +510 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ogcapi-registry
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: Claude, Francesco Bartoli
|
|
6
|
+
Author-email: Claude <noreply@anthropic.com>, Francesco Bartoli <xbartolone@gmail.com>
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: openapi-pydantic>=0.5.1
|
|
9
|
+
Requires-Dist: pydantic>=2.12.5
|
|
10
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# OGC API Registry Validator
|
|
15
|
+
|
|
16
|
+
A Python library for validating OpenAPI documents published by OGC API servers against OGC specification requirements.
|
|
17
|
+
|
|
18
|
+
## Why This Library?
|
|
19
|
+
|
|
20
|
+
OGC API standards (Features, Tiles, Processes, Records, etc.) define specific requirements for the OpenAPI documents that servers must publish. These requirements include:
|
|
21
|
+
|
|
22
|
+
- **Required endpoints** (e.g., `/collections`, `/conformance`)
|
|
23
|
+
- **Required HTTP operations** (GET, POST, etc.)
|
|
24
|
+
- **Conformance class declarations**
|
|
25
|
+
- **Response schemas and content types**
|
|
26
|
+
|
|
27
|
+
This library performs **static validation** of these OpenAPI documents, helping you verify that your OGC API server's published specification meets the standard requirements before deployment.
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **OGC API Validation**: Validate against OGC API - Features, Tiles, Processes, Records, Coverages, EDR, Maps, Styles, Routes
|
|
32
|
+
- **Conformance-Based Detection**: Automatically detect API type from conformance classes
|
|
33
|
+
- **Error Severity Levels**: Distinguish between critical errors, warnings, and informational messages
|
|
34
|
+
- **Extensible Strategy Pattern**: Add custom validation strategies for new specifications
|
|
35
|
+
- **Async Support**: Full async/await support for high-performance validation
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install ogcapi-registry
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or with uv:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv add ogcapi-registry
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Validating an OGC API Server
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from ogcapi_registry import (
|
|
55
|
+
OpenAPIClient,
|
|
56
|
+
validate_ogc_api,
|
|
57
|
+
parse_conformance_classes,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Fetch the OpenAPI document from your server
|
|
61
|
+
client = OpenAPIClient()
|
|
62
|
+
openapi_doc, metadata = client.fetch("https://demo.pygeoapi.io/master/openapi")
|
|
63
|
+
|
|
64
|
+
# Parse conformance classes (usually from /conformance endpoint)
|
|
65
|
+
conformance_uris = [
|
|
66
|
+
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core",
|
|
67
|
+
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson",
|
|
68
|
+
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30",
|
|
69
|
+
]
|
|
70
|
+
conformance_classes = parse_conformance_classes(conformance_uris)
|
|
71
|
+
|
|
72
|
+
# Validate against OGC API requirements
|
|
73
|
+
result = validate_ogc_api(openapi_doc, conformance_classes)
|
|
74
|
+
|
|
75
|
+
if result.is_valid:
|
|
76
|
+
print("OpenAPI document is fully compliant!")
|
|
77
|
+
else:
|
|
78
|
+
# Check error severity
|
|
79
|
+
for error in result.critical_errors:
|
|
80
|
+
print(f"CRITICAL: {error['message']}")
|
|
81
|
+
|
|
82
|
+
for warning in result.warnings:
|
|
83
|
+
print(f"WARNING: {warning['message']}")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Understanding Error Severity
|
|
87
|
+
|
|
88
|
+
The library classifies validation errors by severity:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from ogcapi_registry import ErrorSeverity
|
|
92
|
+
|
|
93
|
+
# Critical errors: Must be fixed for OGC compliance
|
|
94
|
+
result.critical_errors # Missing required endpoints, wrong HTTP methods
|
|
95
|
+
|
|
96
|
+
# Warnings: Should be fixed but API may still function
|
|
97
|
+
result.warnings # Missing recommended features like CRS support
|
|
98
|
+
|
|
99
|
+
# Info: Optional improvements
|
|
100
|
+
result.info_errors # Missing optional features like filtering
|
|
101
|
+
|
|
102
|
+
# Check if API is compliant (no critical errors)
|
|
103
|
+
if result.is_compliant:
|
|
104
|
+
print("API meets minimum OGC requirements")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Automatic API Type Detection
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from ogcapi_registry import detect_api_types, get_primary_api_type
|
|
111
|
+
|
|
112
|
+
# Detect all API types from conformance classes
|
|
113
|
+
api_types = detect_api_types(conformance_classes)
|
|
114
|
+
# e.g., {OGCAPIType.FEATURES, OGCAPIType.TILES}
|
|
115
|
+
|
|
116
|
+
# Get the primary (most specific) API type
|
|
117
|
+
primary = get_primary_api_type(conformance_classes)
|
|
118
|
+
# e.g., OGCAPIType.FEATURES
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Application Example: CI/CD Validation
|
|
122
|
+
|
|
123
|
+
Integrate OGC API validation into your deployment pipeline to catch specification errors before they reach production:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
#!/usr/bin/env python
|
|
127
|
+
"""validate_ogc_server.py - CI/CD validation script"""
|
|
128
|
+
|
|
129
|
+
import sys
|
|
130
|
+
import httpx
|
|
131
|
+
from ogcapi_registry import (
|
|
132
|
+
OpenAPIClient,
|
|
133
|
+
validate_ogc_api,
|
|
134
|
+
parse_conformance_classes,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def validate_server(base_url: str) -> bool:
|
|
139
|
+
"""Validate an OGC API server's OpenAPI document."""
|
|
140
|
+
|
|
141
|
+
# 1. Fetch conformance classes
|
|
142
|
+
response = httpx.get(f"{base_url}/conformance")
|
|
143
|
+
response.raise_for_status()
|
|
144
|
+
conformance_uris = response.json().get("conformsTo", [])
|
|
145
|
+
|
|
146
|
+
if not conformance_uris:
|
|
147
|
+
print("ERROR: No conformance classes declared")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
conformance_classes = parse_conformance_classes(conformance_uris)
|
|
151
|
+
print(f"Found {len(conformance_classes)} conformance classes")
|
|
152
|
+
|
|
153
|
+
# 2. Fetch and validate OpenAPI document
|
|
154
|
+
client = OpenAPIClient()
|
|
155
|
+
openapi_doc, metadata = client.fetch(f"{base_url}/openapi")
|
|
156
|
+
|
|
157
|
+
print(f"Validating OpenAPI {openapi_doc.get('openapi', 'unknown')}")
|
|
158
|
+
|
|
159
|
+
# 3. Run OGC API validation
|
|
160
|
+
result = validate_ogc_api(openapi_doc, conformance_classes)
|
|
161
|
+
|
|
162
|
+
# 4. Report results
|
|
163
|
+
print(f"\n{'='*50}")
|
|
164
|
+
print(f"Validation Result: {'PASSED' if result.is_valid else 'FAILED'}")
|
|
165
|
+
print(f"{'='*50}")
|
|
166
|
+
|
|
167
|
+
if result.critical_errors:
|
|
168
|
+
print(f"\nCritical Errors ({len(result.critical_errors)}):")
|
|
169
|
+
for err in result.critical_errors:
|
|
170
|
+
print(f" - {err['path']}: {err['message']}")
|
|
171
|
+
|
|
172
|
+
if result.warnings:
|
|
173
|
+
print(f"\nWarnings ({len(result.warnings)}):")
|
|
174
|
+
for warn in result.warnings:
|
|
175
|
+
print(f" - {warn['path']}: {warn['message']}")
|
|
176
|
+
|
|
177
|
+
summary = result.get_summary()
|
|
178
|
+
print(f"\nSummary: {summary['critical']} critical, "
|
|
179
|
+
f"{summary['warning']} warnings, {summary['info']} info")
|
|
180
|
+
|
|
181
|
+
# Return True only if no critical errors
|
|
182
|
+
return result.is_compliant
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
server_url = sys.argv[1] if len(sys.argv) > 1 else "https://demo.pygeoapi.io/master"
|
|
187
|
+
|
|
188
|
+
success = validate_server(server_url)
|
|
189
|
+
sys.exit(0 if success else 1)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Use in GitHub Actions:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
- name: Validate OGC API Compliance
|
|
196
|
+
run: python validate_ogc_server.py ${{ env.SERVER_URL }}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Supported OGC API Standards
|
|
200
|
+
|
|
201
|
+
| Standard | Strategy Class | Key Validations |
|
|
202
|
+
|----------|---------------|-----------------|
|
|
203
|
+
| OGC API - Common | `CommonStrategy` | Landing page, conformance, API definition |
|
|
204
|
+
| OGC API - Features | `FeaturesStrategy` | Collections, items, bbox, CRS |
|
|
205
|
+
| OGC API - Tiles | `TilesStrategy` | Tile matrix sets, tile endpoints |
|
|
206
|
+
| OGC API - Processes | `ProcessesStrategy` | Process list, execution, jobs |
|
|
207
|
+
| OGC API - Records | `RecordsStrategy` | Catalog, record queries |
|
|
208
|
+
| OGC API - Coverages | `CoveragesStrategy` | Coverage data access |
|
|
209
|
+
| OGC API - EDR | `EDRStrategy` | Environmental data queries |
|
|
210
|
+
| OGC API - Maps | `MapsStrategy` | Map rendering endpoints |
|
|
211
|
+
| OGC API - Styles | `StylesStrategy` | Style management |
|
|
212
|
+
| OGC API - Routes | `RoutesStrategy` | Routing endpoints |
|
|
213
|
+
|
|
214
|
+
## Advanced Usage
|
|
215
|
+
|
|
216
|
+
### Custom Validation Strategy
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from ogcapi_registry import ValidationStrategy, OGCAPIType, ValidationResult
|
|
220
|
+
|
|
221
|
+
class MyCustomStrategy(ValidationStrategy):
|
|
222
|
+
"""Custom validation for organization-specific requirements."""
|
|
223
|
+
|
|
224
|
+
api_type = OGCAPIType.FEATURES
|
|
225
|
+
|
|
226
|
+
def validate(self, document, conformance_classes):
|
|
227
|
+
errors = []
|
|
228
|
+
|
|
229
|
+
# Add custom validation logic
|
|
230
|
+
if "/health" not in document.get("paths", {}):
|
|
231
|
+
errors.append({
|
|
232
|
+
"path": "paths",
|
|
233
|
+
"message": "Missing /health endpoint (org requirement)",
|
|
234
|
+
"severity": "warning",
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
return ValidationResult.success() if not errors else ValidationResult.failure(errors)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Async Validation
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
import asyncio
|
|
244
|
+
from ogcapi_registry import AsyncOpenAPIClient, validate_ogc_api
|
|
245
|
+
|
|
246
|
+
async def validate_multiple_servers(urls: list[str]):
|
|
247
|
+
client = AsyncOpenAPIClient()
|
|
248
|
+
|
|
249
|
+
async def validate_one(url):
|
|
250
|
+
doc, _ = await client.fetch(f"{url}/openapi")
|
|
251
|
+
# ... fetch conformance and validate
|
|
252
|
+
return url, result
|
|
253
|
+
|
|
254
|
+
results = await asyncio.gather(*[validate_one(url) for url in urls])
|
|
255
|
+
return dict(results)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Documentation
|
|
259
|
+
|
|
260
|
+
Full documentation: [https://francbartoli.github.io/ogcapi-registry/](https://francbartoli.github.io/ogcapi-registry/)
|
|
261
|
+
|
|
262
|
+
## Development
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Clone and install
|
|
266
|
+
git clone https://github.com/francbartoli/ogcapi-registry.git
|
|
267
|
+
cd ogcapi-registry
|
|
268
|
+
uv sync --all-extras --dev
|
|
269
|
+
|
|
270
|
+
# Run tests
|
|
271
|
+
uv run pytest -v
|
|
272
|
+
|
|
273
|
+
# Run linting
|
|
274
|
+
uv run ruff check src/ tests/
|
|
275
|
+
uv run mypy src/ogcapi_registry
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# OGC API Registry Validator
|
|
2
|
+
|
|
3
|
+
A Python library for validating OpenAPI documents published by OGC API servers against OGC specification requirements.
|
|
4
|
+
|
|
5
|
+
## Why This Library?
|
|
6
|
+
|
|
7
|
+
OGC API standards (Features, Tiles, Processes, Records, etc.) define specific requirements for the OpenAPI documents that servers must publish. These requirements include:
|
|
8
|
+
|
|
9
|
+
- **Required endpoints** (e.g., `/collections`, `/conformance`)
|
|
10
|
+
- **Required HTTP operations** (GET, POST, etc.)
|
|
11
|
+
- **Conformance class declarations**
|
|
12
|
+
- **Response schemas and content types**
|
|
13
|
+
|
|
14
|
+
This library performs **static validation** of these OpenAPI documents, helping you verify that your OGC API server's published specification meets the standard requirements before deployment.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **OGC API Validation**: Validate against OGC API - Features, Tiles, Processes, Records, Coverages, EDR, Maps, Styles, Routes
|
|
19
|
+
- **Conformance-Based Detection**: Automatically detect API type from conformance classes
|
|
20
|
+
- **Error Severity Levels**: Distinguish between critical errors, warnings, and informational messages
|
|
21
|
+
- **Extensible Strategy Pattern**: Add custom validation strategies for new specifications
|
|
22
|
+
- **Async Support**: Full async/await support for high-performance validation
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install ogcapi-registry
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or with uv:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv add ogcapi-registry
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Validating an OGC API Server
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from ogcapi_registry import (
|
|
42
|
+
OpenAPIClient,
|
|
43
|
+
validate_ogc_api,
|
|
44
|
+
parse_conformance_classes,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Fetch the OpenAPI document from your server
|
|
48
|
+
client = OpenAPIClient()
|
|
49
|
+
openapi_doc, metadata = client.fetch("https://demo.pygeoapi.io/master/openapi")
|
|
50
|
+
|
|
51
|
+
# Parse conformance classes (usually from /conformance endpoint)
|
|
52
|
+
conformance_uris = [
|
|
53
|
+
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core",
|
|
54
|
+
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson",
|
|
55
|
+
"http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30",
|
|
56
|
+
]
|
|
57
|
+
conformance_classes = parse_conformance_classes(conformance_uris)
|
|
58
|
+
|
|
59
|
+
# Validate against OGC API requirements
|
|
60
|
+
result = validate_ogc_api(openapi_doc, conformance_classes)
|
|
61
|
+
|
|
62
|
+
if result.is_valid:
|
|
63
|
+
print("OpenAPI document is fully compliant!")
|
|
64
|
+
else:
|
|
65
|
+
# Check error severity
|
|
66
|
+
for error in result.critical_errors:
|
|
67
|
+
print(f"CRITICAL: {error['message']}")
|
|
68
|
+
|
|
69
|
+
for warning in result.warnings:
|
|
70
|
+
print(f"WARNING: {warning['message']}")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Understanding Error Severity
|
|
74
|
+
|
|
75
|
+
The library classifies validation errors by severity:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from ogcapi_registry import ErrorSeverity
|
|
79
|
+
|
|
80
|
+
# Critical errors: Must be fixed for OGC compliance
|
|
81
|
+
result.critical_errors # Missing required endpoints, wrong HTTP methods
|
|
82
|
+
|
|
83
|
+
# Warnings: Should be fixed but API may still function
|
|
84
|
+
result.warnings # Missing recommended features like CRS support
|
|
85
|
+
|
|
86
|
+
# Info: Optional improvements
|
|
87
|
+
result.info_errors # Missing optional features like filtering
|
|
88
|
+
|
|
89
|
+
# Check if API is compliant (no critical errors)
|
|
90
|
+
if result.is_compliant:
|
|
91
|
+
print("API meets minimum OGC requirements")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Automatic API Type Detection
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from ogcapi_registry import detect_api_types, get_primary_api_type
|
|
98
|
+
|
|
99
|
+
# Detect all API types from conformance classes
|
|
100
|
+
api_types = detect_api_types(conformance_classes)
|
|
101
|
+
# e.g., {OGCAPIType.FEATURES, OGCAPIType.TILES}
|
|
102
|
+
|
|
103
|
+
# Get the primary (most specific) API type
|
|
104
|
+
primary = get_primary_api_type(conformance_classes)
|
|
105
|
+
# e.g., OGCAPIType.FEATURES
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Application Example: CI/CD Validation
|
|
109
|
+
|
|
110
|
+
Integrate OGC API validation into your deployment pipeline to catch specification errors before they reach production:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
#!/usr/bin/env python
|
|
114
|
+
"""validate_ogc_server.py - CI/CD validation script"""
|
|
115
|
+
|
|
116
|
+
import sys
|
|
117
|
+
import httpx
|
|
118
|
+
from ogcapi_registry import (
|
|
119
|
+
OpenAPIClient,
|
|
120
|
+
validate_ogc_api,
|
|
121
|
+
parse_conformance_classes,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def validate_server(base_url: str) -> bool:
|
|
126
|
+
"""Validate an OGC API server's OpenAPI document."""
|
|
127
|
+
|
|
128
|
+
# 1. Fetch conformance classes
|
|
129
|
+
response = httpx.get(f"{base_url}/conformance")
|
|
130
|
+
response.raise_for_status()
|
|
131
|
+
conformance_uris = response.json().get("conformsTo", [])
|
|
132
|
+
|
|
133
|
+
if not conformance_uris:
|
|
134
|
+
print("ERROR: No conformance classes declared")
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
conformance_classes = parse_conformance_classes(conformance_uris)
|
|
138
|
+
print(f"Found {len(conformance_classes)} conformance classes")
|
|
139
|
+
|
|
140
|
+
# 2. Fetch and validate OpenAPI document
|
|
141
|
+
client = OpenAPIClient()
|
|
142
|
+
openapi_doc, metadata = client.fetch(f"{base_url}/openapi")
|
|
143
|
+
|
|
144
|
+
print(f"Validating OpenAPI {openapi_doc.get('openapi', 'unknown')}")
|
|
145
|
+
|
|
146
|
+
# 3. Run OGC API validation
|
|
147
|
+
result = validate_ogc_api(openapi_doc, conformance_classes)
|
|
148
|
+
|
|
149
|
+
# 4. Report results
|
|
150
|
+
print(f"\n{'='*50}")
|
|
151
|
+
print(f"Validation Result: {'PASSED' if result.is_valid else 'FAILED'}")
|
|
152
|
+
print(f"{'='*50}")
|
|
153
|
+
|
|
154
|
+
if result.critical_errors:
|
|
155
|
+
print(f"\nCritical Errors ({len(result.critical_errors)}):")
|
|
156
|
+
for err in result.critical_errors:
|
|
157
|
+
print(f" - {err['path']}: {err['message']}")
|
|
158
|
+
|
|
159
|
+
if result.warnings:
|
|
160
|
+
print(f"\nWarnings ({len(result.warnings)}):")
|
|
161
|
+
for warn in result.warnings:
|
|
162
|
+
print(f" - {warn['path']}: {warn['message']}")
|
|
163
|
+
|
|
164
|
+
summary = result.get_summary()
|
|
165
|
+
print(f"\nSummary: {summary['critical']} critical, "
|
|
166
|
+
f"{summary['warning']} warnings, {summary['info']} info")
|
|
167
|
+
|
|
168
|
+
# Return True only if no critical errors
|
|
169
|
+
return result.is_compliant
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
server_url = sys.argv[1] if len(sys.argv) > 1 else "https://demo.pygeoapi.io/master"
|
|
174
|
+
|
|
175
|
+
success = validate_server(server_url)
|
|
176
|
+
sys.exit(0 if success else 1)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Use in GitHub Actions:
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
182
|
+
- name: Validate OGC API Compliance
|
|
183
|
+
run: python validate_ogc_server.py ${{ env.SERVER_URL }}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Supported OGC API Standards
|
|
187
|
+
|
|
188
|
+
| Standard | Strategy Class | Key Validations |
|
|
189
|
+
|----------|---------------|-----------------|
|
|
190
|
+
| OGC API - Common | `CommonStrategy` | Landing page, conformance, API definition |
|
|
191
|
+
| OGC API - Features | `FeaturesStrategy` | Collections, items, bbox, CRS |
|
|
192
|
+
| OGC API - Tiles | `TilesStrategy` | Tile matrix sets, tile endpoints |
|
|
193
|
+
| OGC API - Processes | `ProcessesStrategy` | Process list, execution, jobs |
|
|
194
|
+
| OGC API - Records | `RecordsStrategy` | Catalog, record queries |
|
|
195
|
+
| OGC API - Coverages | `CoveragesStrategy` | Coverage data access |
|
|
196
|
+
| OGC API - EDR | `EDRStrategy` | Environmental data queries |
|
|
197
|
+
| OGC API - Maps | `MapsStrategy` | Map rendering endpoints |
|
|
198
|
+
| OGC API - Styles | `StylesStrategy` | Style management |
|
|
199
|
+
| OGC API - Routes | `RoutesStrategy` | Routing endpoints |
|
|
200
|
+
|
|
201
|
+
## Advanced Usage
|
|
202
|
+
|
|
203
|
+
### Custom Validation Strategy
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from ogcapi_registry import ValidationStrategy, OGCAPIType, ValidationResult
|
|
207
|
+
|
|
208
|
+
class MyCustomStrategy(ValidationStrategy):
|
|
209
|
+
"""Custom validation for organization-specific requirements."""
|
|
210
|
+
|
|
211
|
+
api_type = OGCAPIType.FEATURES
|
|
212
|
+
|
|
213
|
+
def validate(self, document, conformance_classes):
|
|
214
|
+
errors = []
|
|
215
|
+
|
|
216
|
+
# Add custom validation logic
|
|
217
|
+
if "/health" not in document.get("paths", {}):
|
|
218
|
+
errors.append({
|
|
219
|
+
"path": "paths",
|
|
220
|
+
"message": "Missing /health endpoint (org requirement)",
|
|
221
|
+
"severity": "warning",
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
return ValidationResult.success() if not errors else ValidationResult.failure(errors)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Async Validation
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
import asyncio
|
|
231
|
+
from ogcapi_registry import AsyncOpenAPIClient, validate_ogc_api
|
|
232
|
+
|
|
233
|
+
async def validate_multiple_servers(urls: list[str]):
|
|
234
|
+
client = AsyncOpenAPIClient()
|
|
235
|
+
|
|
236
|
+
async def validate_one(url):
|
|
237
|
+
doc, _ = await client.fetch(f"{url}/openapi")
|
|
238
|
+
# ... fetch conformance and validate
|
|
239
|
+
return url, result
|
|
240
|
+
|
|
241
|
+
results = await asyncio.gather(*[validate_one(url) for url in urls])
|
|
242
|
+
return dict(results)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Documentation
|
|
246
|
+
|
|
247
|
+
Full documentation: [https://francbartoli.github.io/ogcapi-registry/](https://francbartoli.github.io/ogcapi-registry/)
|
|
248
|
+
|
|
249
|
+
## Development
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# Clone and install
|
|
253
|
+
git clone https://github.com/francbartoli/ogcapi-registry.git
|
|
254
|
+
cd ogcapi-registry
|
|
255
|
+
uv sync --all-extras --dev
|
|
256
|
+
|
|
257
|
+
# Run tests
|
|
258
|
+
uv run pytest -v
|
|
259
|
+
|
|
260
|
+
# Run linting
|
|
261
|
+
uv run ruff check src/ tests/
|
|
262
|
+
uv run mypy src/ogcapi_registry
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ogcapi-registry"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Claude", email = "noreply@anthropic.com" },
|
|
8
|
+
{ name = "Francesco Bartoli", email = "xbartolone@gmail.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"httpx>=0.28.1",
|
|
13
|
+
"openapi-pydantic>=0.5.1",
|
|
14
|
+
"pydantic>=2.12.5",
|
|
15
|
+
"pyyaml>=6.0.3",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["uv_build>=0.8.17,<0.9.0"]
|
|
20
|
+
build-backend = "uv_build"
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"mkdocs>=1.6.1",
|
|
25
|
+
"mkdocs-material>=9.7.0",
|
|
26
|
+
"mypy>=1.19.0",
|
|
27
|
+
"pytest>=9.0.1",
|
|
28
|
+
"pytest-asyncio>=1.3.0",
|
|
29
|
+
"pytest-cov>=6.0.0",
|
|
30
|
+
"pytest-httpx>=0.36.0",
|
|
31
|
+
"ruff>=0.14.8",
|
|
32
|
+
"types-pyyaml>=6.0.12.20250915",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.mypy]
|
|
36
|
+
python_version = "3.11"
|
|
37
|
+
warn_unused_ignores = true
|
|
38
|
+
plugins = ["pydantic.mypy"]
|
|
39
|
+
# Ignore missing imports for packages without type stubs
|
|
40
|
+
ignore_missing_imports = true
|
|
41
|
+
|
|
42
|
+
[tool.pydantic-mypy]
|
|
43
|
+
init_forbid_extra = true
|
|
44
|
+
init_typed = true
|
|
45
|
+
warn_required_dynamic_aliases = true
|