|
5 | 5 | from urllib.parse import urlparse
|
6 | 6 | from urllib.request import Request, urlopen
|
7 | 7 |
|
8 |
| -import jsonschema |
9 | 8 | import requests # type: ignore
|
10 | 9 | from jsonschema import Draft202012Validator
|
11 | 10 | from referencing import Registry, Resource
|
12 | 11 | from referencing.jsonschema import DRAFT202012
|
13 |
| -from referencing.retrieval import to_cached_resource |
14 | 12 | from referencing.typing import URI
|
15 | 13 |
|
16 | 14 | NEW_VERSIONS = [
|
@@ -192,88 +190,75 @@ def link_request(
|
192 | 190 | initial_message["format_invalid"].append(link["href"])
|
193 | 191 |
|
194 | 192 |
|
195 |
| -def fetch_remote_schema(uri: str) -> dict: |
| 193 | +def cached_retrieve(uri: URI, schema_map: Optional[Dict] = None) -> Resource[Dict]: |
196 | 194 | """
|
197 |
| - Fetch a remote schema from a URI. |
| 195 | + Retrieve and cache a remote schema. |
198 | 196 |
|
199 | 197 | Args:
|
200 |
| - uri (str): The URI of the schema to fetch. |
| 198 | + uri (str): The URI of the schema. |
| 199 | + schema_map_keys: Override schema location to validate against local versions of a schema |
201 | 200 |
|
202 | 201 | Returns:
|
203 |
| - dict: The fetched schema content as a dictionary. |
| 202 | + dict: The parsed JSON dict of the schema. |
204 | 203 |
|
205 | 204 | Raises:
|
206 | 205 | requests.RequestException: If the request to fetch the schema fails.
|
| 206 | + Exception: For any other unexpected errors. |
207 | 207 | """
|
208 |
| - response = requests.get(uri) |
209 |
| - response.raise_for_status() |
210 |
| - return response.json() |
| 208 | + return Resource.from_contents( |
| 209 | + fetch_schema_with_override(uri, schema_map=schema_map) |
| 210 | + ) |
211 | 211 |
|
212 | 212 |
|
213 |
| -@to_cached_resource() # type: ignore |
214 |
| -def cached_retrieve(uri: URI) -> str: |
| 213 | +def fetch_schema_with_override( |
| 214 | + schema_path: str, schema_map: Optional[Dict] = None |
| 215 | +) -> Dict: |
215 | 216 | """
|
216 | 217 | Retrieve and cache a remote schema.
|
217 | 218 |
|
218 | 219 | Args:
|
219 |
| - uri (str): The URI of the schema. |
| 220 | + schema_path (str): Path or URI of the schema. |
| 221 | + schema_map (dict): Override schema location to validate against local versions of a schema |
220 | 222 |
|
221 | 223 | Returns:
|
222 |
| - str: The raw JSON string of the schema. |
223 |
| -
|
224 |
| - Raises: |
225 |
| - requests.RequestException: If the request to fetch the schema fails. |
226 |
| - Exception: For any other unexpected errors. |
| 224 | + dict: The parsed JSON dict of the schema. |
227 | 225 | """
|
228 |
| - try: |
229 |
| - response = requests.get(uri, timeout=10) # Set a timeout for robustness |
230 |
| - response.raise_for_status() # Raise an error for HTTP response codes >= 400 |
231 |
| - return response.text |
232 |
| - except requests.exceptions.RequestException as e: |
233 |
| - raise requests.RequestException( |
234 |
| - f"Failed to fetch schema from {uri}: {str(e)}" |
235 |
| - ) from e |
236 |
| - except Exception as e: |
237 |
| - raise Exception( |
238 |
| - f"Unexpected error while retrieving schema from {uri}: {str(e)}" |
239 |
| - ) from e |
240 |
| - |
241 |
| - |
242 |
| -def validate_with_ref_resolver(schema_path: str, content: dict) -> None: |
| 226 | + |
| 227 | + if schema_map: |
| 228 | + if schema_path in schema_map: |
| 229 | + schema_path = schema_map[schema_path] |
| 230 | + |
| 231 | + # Load the schema |
| 232 | + return fetch_and_parse_schema(schema_path) |
| 233 | + |
| 234 | + |
| 235 | +def validate_with_ref_resolver( |
| 236 | + schema_path: str, content: Dict, schema_map: Optional[Dict] = None |
| 237 | +) -> None: |
243 | 238 | """
|
244 | 239 | Validate a JSON document against a JSON Schema with dynamic reference resolution.
|
245 | 240 |
|
246 | 241 | Args:
|
247 | 242 | schema_path (str): Path or URI of the JSON Schema.
|
248 | 243 | content (dict): JSON content to validate.
|
| 244 | + schema_map (dict): Override schema location to validate against local versions of a schema |
249 | 245 |
|
250 | 246 | Raises:
|
251 | 247 | jsonschema.exceptions.ValidationError: If validation fails.
|
252 | 248 | requests.RequestException: If fetching a remote schema fails.
|
253 | 249 | FileNotFoundError: If a local schema file is not found.
|
254 | 250 | Exception: If any other error occurs during validation.
|
255 | 251 | """
|
256 |
| - # Load the schema |
257 |
| - if schema_path.startswith("http"): |
258 |
| - schema = fetch_remote_schema(schema_path) |
259 |
| - else: |
260 |
| - try: |
261 |
| - with open(schema_path, "r") as f: |
262 |
| - schema = json.load(f) |
263 |
| - except FileNotFoundError as e: |
264 |
| - raise FileNotFoundError(f"Schema file not found: {schema_path}") from e |
265 |
| - |
| 252 | + schema = fetch_schema_with_override(schema_path, schema_map=schema_map) |
266 | 253 | # Set up the resource and registry for schema resolution
|
| 254 | + cached_retrieve_with_schema_map = functools.partial( |
| 255 | + cached_retrieve, schema_map=schema_map |
| 256 | + ) |
267 | 257 | resource: Resource = Resource(contents=schema, specification=DRAFT202012) # type: ignore
|
268 |
| - registry: Registry = Registry(retrieve=cached_retrieve).with_resource( # type: ignore |
| 258 | + registry: Registry = Registry(retrieve=cached_retrieve_with_schema_map).with_resource( # type: ignore |
269 | 259 | uri=schema_path, resource=resource
|
270 | 260 | ) # type: ignore
|
271 | 261 |
|
272 | 262 | # Validate the content against the schema
|
273 |
| - try: |
274 |
| - validator = Draft202012Validator(schema, registry=registry) |
275 |
| - validator.validate(content) |
276 |
| - except jsonschema.exceptions.ValidationError as e: |
277 |
| - raise jsonschema.exceptions.ValidationError(f"{e.message}") from e |
278 |
| - except Exception as e: |
279 |
| - raise Exception(f"Unexpected error during validation: {str(e)}") from e |
| 263 | + validator = Draft202012Validator(schema, registry=registry) |
| 264 | + validator.validate(content) |
0 commit comments