Skip to content

Commit 7dfd92c

Browse files
committed
Add seen_map
1 parent 448e36a commit 7dfd92c

File tree

2 files changed

+32
-25
lines changed

2 files changed

+32
-25
lines changed

src/hypothesis_jsonschema/_canonicalise.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
import math
1818
import re
1919
from 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

2223
import jsonschema
2324
from hypothesis.errors import InvalidArgument
@@ -567,15 +568,14 @@ def canonicalish(
567568

568569

569570
def 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()

src/hypothesis_jsonschema/_from_schema.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
FALSEY,
1919
TRUTHY,
2020
TYPE_STRINGS,
21-
JSONType,
2221
LocalResolver,
2322
Schema,
2423
canonicalish,

0 commit comments

Comments
 (0)