oapi-profile-builder 3.2.0__tar.gz → 3.2.1__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 (20) hide show
  1. {oapi_profile_builder-3.2.0/src/oapi_profile_builder.egg-info → oapi_profile_builder-3.2.1}/PKG-INFO +1 -1
  2. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/pyproject.toml +1 -1
  3. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/server_validation.py +91 -0
  4. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1/src/oapi_profile_builder.egg-info}/PKG-INFO +1 -1
  5. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/LICENSE +0 -0
  6. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/README.md +0 -0
  7. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/setup.cfg +0 -0
  8. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/__init__.py +0 -0
  9. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/cite.py +0 -0
  10. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/cite_features.py +0 -0
  11. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/cli.py +0 -0
  12. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/compile.py +0 -0
  13. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/cover.py +0 -0
  14. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/generate.py +0 -0
  15. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder/models.py +0 -0
  16. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder.egg-info/SOURCES.txt +0 -0
  17. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder.egg-info/dependency_links.txt +0 -0
  18. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder.egg-info/entry_points.txt +0 -0
  19. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/src/oapi_profile_builder.egg-info/requires.txt +0 -0
  20. {oapi_profile_builder-3.2.0 → oapi_profile_builder-3.2.1}/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: 3.2.0
3
+ Version: 3.2.1
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "oapi-profile-builder"
7
- version = "3.2.0"
7
+ version = "3.2.1"
8
8
  description = "Authoritative tooling for creating OGC API Service Profiles (EDR, Features)"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -39,6 +39,7 @@ import subprocess
39
39
  import sys
40
40
  import tempfile
41
41
  from pathlib import Path
42
+ from urllib.parse import urljoin, urlsplit
42
43
 
43
44
  import yaml
44
45
 
@@ -60,6 +61,72 @@ VALID_CHECKS = {
60
61
  }
61
62
 
62
63
 
64
+ def _discover_service_desc_path(base_url: str) -> str | None:
65
+ """
66
+ Fetch the landing page and resolve where the live server actually serves
67
+ its OpenAPI document.
68
+
69
+ OGC API standards do not fix the OpenAPI document path. It is discoverable
70
+ via the landing page link with rel="service-desc" (machine-readable). This
71
+ returns the path relative to ``base_url`` (e.g. "/openapi.json") suitable
72
+ for use as an OpenAPI path template, or None if it cannot be resolved to a
73
+ path on the same host as ``base_url``.
74
+ """
75
+ try:
76
+ import requests
77
+ except ImportError:
78
+ return None
79
+
80
+ # Normalise to a base that urljoin treats as a directory.
81
+ base = base_url if base_url.endswith("/") else base_url + "/"
82
+
83
+ try:
84
+ resp = requests.get(
85
+ base,
86
+ headers={"Accept": "application/json"},
87
+ timeout=10,
88
+ )
89
+ resp.raise_for_status()
90
+ landing = resp.json()
91
+ except Exception:
92
+ return None
93
+
94
+ if not isinstance(landing, dict):
95
+ return None
96
+
97
+ links = landing.get("links")
98
+ if not isinstance(links, list):
99
+ return None
100
+
101
+ href = None
102
+ for link in links:
103
+ if isinstance(link, dict) and link.get("rel") == "service-desc" and link.get("href"):
104
+ href = link["href"]
105
+ break
106
+ if not href:
107
+ return None
108
+
109
+ # Resolve relative hrefs against the landing page URL.
110
+ absolute = urljoin(base, href)
111
+
112
+ base_parts = urlsplit(base)
113
+ href_parts = urlsplit(absolute)
114
+
115
+ # schemathesis appends the OpenAPI path template to --url, so the spec can
116
+ # only describe resources on the same origin as the validated base URL.
117
+ if (href_parts.scheme, href_parts.netloc) != (base_parts.scheme, base_parts.netloc):
118
+ return None
119
+
120
+ # Return the href path relative to the base URL's path.
121
+ base_path = base_parts.path # ends with "/"
122
+ href_path = href_parts.path or "/"
123
+ if href_path.startswith(base_path):
124
+ rel = href_path[len(base_path):]
125
+ else:
126
+ rel = href_path.lstrip("/")
127
+ return "/" + rel
128
+
129
+
63
130
  def validate_server(
64
131
  spec: dict,
65
132
  url: str,
@@ -88,6 +155,27 @@ def validate_server(
88
155
  )
89
156
  sys.exit(1)
90
157
 
158
+ # The OpenAPI document path is discoverable, not fixed by OGC API. The spec
159
+ # ships a placeholder "/openapi" path; remap it to wherever the live server
160
+ # actually advertises its service-desc link, or drop it if we cannot resolve
161
+ # a same-origin path (testing a path the standard never mandated would be a
162
+ # false failure).
163
+ drop_openapi_path = False
164
+ paths = spec.get("paths")
165
+ if isinstance(paths, dict) and "/openapi" in paths:
166
+ discovered = _discover_service_desc_path(url)
167
+ if discovered and discovered != "/openapi":
168
+ if discovered in paths:
169
+ # Server serves its spec at a path the profile already describes.
170
+ paths.pop("/openapi")
171
+ else:
172
+ paths[discovered] = paths.pop("/openapi")
173
+ print(f"Discovered service-desc document at: {discovered}")
174
+ elif not discovered:
175
+ drop_openapi_path = True
176
+ print("Could not resolve service-desc from landing page; "
177
+ "excluding /openapi from validation.")
178
+
91
179
  with tempfile.NamedTemporaryFile(
92
180
  mode="w", suffix=".yaml", delete=False, encoding="utf-8"
93
181
  ) as tmp:
@@ -108,6 +196,9 @@ def validate_server(
108
196
  if exclude_pattern:
109
197
  cmd += ["--exclude-path-regex", exclude_pattern.pattern]
110
198
 
199
+ if drop_openapi_path:
200
+ cmd += ["--exclude-path-regex", r"^/openapi$"]
201
+
111
202
  # Exclude POST /execution and job resource paths unless stateful testing is enabled
112
203
  if not stateful:
113
204
  cmd += ["--exclude-path-regex", r"(/execution$|/jobs/[^/]+)"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oapi-profile-builder
3
- Version: 3.2.0
3
+ Version: 3.2.1
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