1717import math
1818import re
1919from copy import deepcopy
20- from typing import Any , Dict , List , NoReturn , Optional , Tuple , Union
20+ from typing import Any , Dict , List , NoReturn , Optional , Set , Tuple , Union
21+ from urllib .parse import urljoin
2122
2223import jsonschema
2324from hypothesis .errors import InvalidArgument
@@ -85,9 +86,9 @@ def _get_validator_class(schema: Schema) -> JSONSchemaValidator:
8586 return validator
8687
8788
88- def make_validator (schema : Schema ) -> JSONSchemaValidator :
89+ def make_validator (schema : Schema , resolver : LocalResolver ) -> JSONSchemaValidator :
8990 validator = _get_validator_class (schema )
90- return validator (schema )
91+ return validator (schema , resolver = resolver )
9192
9293
9394class HypothesisRefResolutionError (jsonschema .exceptions .RefResolutionError ):
@@ -567,15 +568,14 @@ def canonicalish(
567568
568569
569570def resolve_all_refs (
570- schema : Union [bool , Schema ], * , resolver : LocalResolver = None
571+ schema : Union [bool , Schema ],
572+ * ,
573+ resolver : LocalResolver = None ,
574+ seen_map : Dict [str , Set [str ]] = None ,
571575) -> Schema :
572- """
573- Resolve all references in the given schema.
574-
575- This handles nested definitions, but not recursive definitions.
576- The latter require special handling to convert to strategies and are much
577- less common, so we just ignore them (and error out) for now.
578- """
576+ """Resolve all non-recursive references in the given schema."""
577+ if seen_map is None :
578+ seen_map = {}
579579 if isinstance (schema , bool ):
580580 return canonicalish (schema )
581581 assert isinstance (schema , dict ), schema
@@ -587,41 +587,49 @@ def resolve_all_refs(
587587 )
588588
589589 def is_recursive (reference : str ) -> bool :
590- return reference == "#" or reference in resolver ._scopes_stack # type: ignore
590+ full_ref = urljoin (resolver .base_uri , reference ) # type: ignore
591+ return reference == "#" or reference in resolver ._scopes_stack or full_ref in resolver ._scopes_stack # type: ignore
591592
592593 # To avoid infinite recursion, we skip all recursive definitions, and such references will be processed later
593594 # A definition is recursive if it contains a reference to itself or one of its ancestors.
594- if "$ref" in schema and not is_recursive (schema ["$ref" ]): # type: ignore
595- s = dict (schema )
596- ref = s .pop ("$ref" )
597- with resolver .resolving (ref ) as got :
598- if s == {}:
599- return resolve_all_refs (got , resolver = resolver )
600- m = merged ([s , got ], resolver = resolver )
601- if m is None : # pragma: no cover
602- msg = f"$ref:{ ref !r} had incompatible base schema { s !r} "
603- raise HypothesisRefResolutionError (msg )
604- return resolve_all_refs (m , resolver = resolver )
595+ if "$ref" in schema :
596+ path = "-" .join (resolver ._scopes_stack )
597+ seen_paths = seen_map .setdefault (path , set ())
598+ if schema ["$ref" ] not in seen_paths and not is_recursive (schema ["$ref" ]): # type: ignore
599+ seen_paths .add (schema ["$ref" ]) # type: ignore
600+ s = dict (schema )
601+ ref = s .pop ("$ref" )
602+ with resolver .resolving (ref ) as got :
603+ if s == {}:
604+ return resolve_all_refs (got , resolver = resolver , seen_map = seen_map )
605+ m = merged ([s , got ])
606+ if m is None : # pragma: no cover
607+ msg = f"$ref:{ ref !r} had incompatible base schema { s !r} "
608+ raise HypothesisRefResolutionError (msg )
609+
610+ return resolve_all_refs (m , resolver = resolver , seen_map = seen_map )
605611
606612 for key in SCHEMA_KEYS :
607613 val = schema .get (key , False )
608614 if isinstance (val , list ):
609615 schema [key ] = [
610- resolve_all_refs (deepcopy (v ), resolver = resolver )
616+ resolve_all_refs (deepcopy (v ), resolver = resolver , seen_map = seen_map )
611617 if isinstance (v , dict )
612618 else v
613619 for v in val
614620 ]
615621 elif isinstance (val , dict ):
616- schema [key ] = resolve_all_refs (deepcopy (val ), resolver = resolver )
622+ schema [key ] = resolve_all_refs (
623+ deepcopy (val ), resolver = resolver , seen_map = seen_map
624+ )
617625 else :
618626 assert isinstance (val , bool )
619627 for key in SCHEMA_OBJECT_KEYS : # values are keys-to-schema-dicts, not schemas
620628 if key in schema :
621629 subschema = schema [key ]
622630 assert isinstance (subschema , dict )
623631 schema [key ] = {
624- k : resolve_all_refs (deepcopy (v ), resolver = resolver )
632+ k : resolve_all_refs (deepcopy (v ), resolver = resolver , seen_map = seen_map )
625633 if isinstance (v , dict )
626634 else v
627635 for k , v in subschema .items ()
0 commit comments