|
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