Skip to content

Commit 3fdb280

Browse files
authored
Merge pull request #300 from cwacek/fix/292
Fix 292 by adding intermediary class builder.
2 parents 6cf6fc1 + 55cab3d commit 3fdb280

File tree

3 files changed

+181
-5
lines changed

3 files changed

+181
-5
lines changed

conftest.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import importlib.resources
2+
import importlib.resources
23
import json
34

45
import pytest
@@ -8,9 +9,16 @@
89

910
@pytest.fixture
1011
def markdown_examples():
11-
with importlib.resources.path(
12-
"python_jsonschema_objects.examples", "README.md"
13-
) as md:
12+
if hasattr(importlib.resources, "as_file"):
13+
filehandle = importlib.resources.as_file(
14+
importlib.resources.files("python_jsonschema_objects.examples")
15+
/ "README.md"
16+
)
17+
else:
18+
filehandle = importlib.resources.path(
19+
"python_jsonschema_objects.examples", "README.md"
20+
)
21+
with filehandle as md:
1422
examples = pjs.markdown_support.extract_code_blocks(md)
1523

1624
return {json.loads(v)["title"]: json.loads(v) for v in examples["schema"]}

python_jsonschema_objects/classbuilder.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import collections.abc
2+
from urllib.parse import urldefrag, urljoin
23
import copy
34
import itertools
45
import logging
@@ -491,8 +492,17 @@ def resolve_type(self, ref, source):
491492
"Resolving direct reference object {0} -> {1}", source, uri
492493
)
493494
)
494-
resolved = self.resolver.lookup(ref)
495-
self.resolved[uri] = self.construct(uri, resolved.contents, (ProtocolBase,))
495+
resolved = self.resolver.lookup(uri)
496+
if resolved.resolver != self.resolver:
497+
sub_cb = ClassBuilder(resolved.resolver)
498+
self.resolved[uri] = sub_cb.construct(
499+
uri, resolved.contents, (ProtocolBase,)
500+
)
501+
else:
502+
self.resolved[uri] = self.construct(
503+
uri, resolved.contents, (ProtocolBase,)
504+
)
505+
496506
return self.resolved[uri]
497507

498508
def construct(self, uri, *args, **kw):

test/test_292.py

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import logging
2+
3+
import pytest
4+
5+
import python_jsonschema_objects as pjs
6+
import referencing
7+
8+
SCHEMA_A = {
9+
"$id": "schema-a",
10+
"$schema": "http://json-schema.org/draft-04/schema#",
11+
"title": "Schema A",
12+
"definitions": {
13+
"myint": {"type": "integer", "minimum": 42},
14+
"myintarray": {
15+
"type": "array",
16+
"items": {
17+
"$ref": "#/definitions/myint", # using 'schema-a#/definitions/myint' would work
18+
},
19+
},
20+
"myintref": {
21+
"$ref": "#/definitions/myint", # using 'schema-a#/definitions/myint' would work
22+
},
23+
},
24+
"type": "object",
25+
"properties": {
26+
"theint": {
27+
"$ref": "#/definitions/myint", # using 'schema-a#/definitions/myint' would work
28+
},
29+
},
30+
}
31+
32+
33+
def test_referenced_schema_works_indirectly():
34+
registry = referencing.Registry().with_resources(
35+
[
36+
("schema-a", referencing.Resource.from_contents(SCHEMA_A)),
37+
]
38+
)
39+
40+
# works fine
41+
builder_a = pjs.ObjectBuilder(SCHEMA_A, registry=registry)
42+
namespace_a = builder_a.build_classes(named_only=False)
43+
44+
b = namespace_a.SchemaA()
45+
b.obja = {"theint": 42}
46+
b.theintarray = [42, 43, 44]
47+
b.theintref = 42
48+
print(b.for_json())
49+
50+
51+
@pytest.mark.parametrize(
52+
"ref",
53+
[
54+
"schema-a#/definitions/myint",
55+
"schema-a#/definitions/myintref", # This is an interesting variation, because this ref is itself a ref.
56+
],
57+
)
58+
def test_referenced_schema_works_directly(ref):
59+
registry = referencing.Registry().with_resources(
60+
[
61+
("schema-a", referencing.Resource.from_contents(SCHEMA_A)),
62+
]
63+
)
64+
65+
# WE make a dumb schema that references this
66+
schema = {
67+
"$schema": "http://json-schema.org/draft-04/schema#",
68+
"$id": "test",
69+
"title": "Test",
70+
"type": "object",
71+
"properties": {"name": {"$ref": ref}},
72+
}
73+
74+
# works fine
75+
builder_a = pjs.ObjectBuilder(schema, registry=registry)
76+
namespace_a = builder_a.build_classes(named_only=False)
77+
78+
b = namespace_a.Test()
79+
b.name = 42
80+
print(b.for_json())
81+
82+
83+
def test_you_do_actually_need_a_reference():
84+
logging.basicConfig(level=logging.DEBUG)
85+
86+
registry = referencing.Registry().with_resources(
87+
[
88+
("schema-a", referencing.Resource.from_contents(SCHEMA_A)),
89+
]
90+
)
91+
92+
# WE make a dumb schema that references
93+
schema = {
94+
"$schema": "http://json-schema.org/draft-04/schema#",
95+
"$id": "test",
96+
"title": "Test",
97+
"type": "object",
98+
"properties": {
99+
"name": {
100+
"$ref": "#/definitions/myint",
101+
}
102+
},
103+
}
104+
105+
# works fine
106+
builder_a = pjs.ObjectBuilder(schema, registry=registry)
107+
with pytest.raises(Exception):
108+
# WE would expect this to fail because this isn't actually
109+
# a good reference. Our schema doesn't have a definitions block,
110+
# so schema-a should be a required URI
111+
builder_a.build_classes(named_only=False)
112+
113+
114+
def test_regression_292():
115+
SCHEMA_B = {
116+
"$id": "schema-b",
117+
"$schema": "http://json-schema.org/draft-04/schema#",
118+
"title": "Schema B",
119+
"type": "object",
120+
"definitions": {
121+
"myintref": {
122+
"$ref": "schema-a#/definitions/myint",
123+
},
124+
},
125+
"properties": {
126+
# all three properties cause the failure
127+
"obja": {
128+
"$ref": "schema-a",
129+
},
130+
"theintarray": {
131+
"$ref": "schema-a#/definitions/myintarray",
132+
},
133+
"thedirectintref": {
134+
"$ref": "schema-a#/definitions/myint",
135+
},
136+
"theintref": {
137+
"$ref": "#/definitions/myintref",
138+
},
139+
},
140+
}
141+
142+
registry = referencing.Registry().with_resources(
143+
[
144+
("schema-a", referencing.Resource.from_contents(SCHEMA_A)),
145+
]
146+
)
147+
148+
# fails
149+
builder_b = pjs.ObjectBuilder(SCHEMA_B, registry=registry)
150+
namespace_b = builder_b.build_classes(
151+
named_only=False
152+
) # referencing.exceptions.PointerToNowhere: '/definitions/myint' does not exist within SCHEMA_B
153+
154+
b = namespace_b.SchemaB()
155+
b.obja = {"theint": 42}
156+
b.theintarray = [42, 43, 44]
157+
b.theintref = 42
158+
print(b.for_json())

0 commit comments

Comments
 (0)