@@ -15,13 +15,59 @@ class UnidentifiedResource(Exception):
15
15
pass
16
16
17
17
18
+ @frozen
19
+ class IdentifiedResource :
20
+
21
+ _specification : Specification
22
+ resource : Schema
23
+
24
+ @classmethod
25
+ def from_resource (cls , resource , ** kwargs ):
26
+ return cls (
27
+ resource = resource ,
28
+ specification = specification_for (resource , ** kwargs ),
29
+ )
30
+
31
+ def id (self ):
32
+ return self ._specification .id_of (self .resource )
33
+
34
+ def anchors (self ):
35
+ return self ._specification .anchors_in (self .resource )
36
+
37
+ def subresources (self ):
38
+ for each in self ._specification .subresources_of (self .resource ):
39
+ yield IdentifiedResource .from_resource (
40
+ resource = each ,
41
+ default = self ._specification ,
42
+ )
43
+
44
+
45
+ def specification_for (
46
+ resource : Schema ,
47
+ default : Specification = ..., # type: ignore
48
+ ) -> Specification :
49
+ if resource is True or resource is False :
50
+ pass
51
+ else :
52
+ jsonschema_schema_keyword = resource .get ("$schema" )
53
+ if jsonschema_schema_keyword is not None :
54
+ from referencing import jsonschema
55
+
56
+ specification = jsonschema .BY_ID .get (jsonschema_schema_keyword )
57
+ if specification is not None :
58
+ return specification
59
+ if default is ...:
60
+ raise UnidentifiedResource (resource )
61
+ return default
62
+
63
+
18
64
@frozen
19
65
class Anchor :
20
66
21
67
name : str
22
- resource : Schema
68
+ resource : IdentifiedResource
23
69
24
- def resolve (self , resolver : Resolver , uri : str ) -> tuple [ Schema , str ] :
70
+ def resolve (self , resolver , uri ) :
25
71
return self .resource , uri
26
72
27
73
@@ -47,12 +93,14 @@ def subresources_of(self, resource):
47
93
@frozen
48
94
class Registry :
49
95
50
- _contents : PMap [str , tuple [Schema , PMap [str , AnchorType ]]] = field (
96
+ _contents : PMap [
97
+ str ,
98
+ tuple [IdentifiedResource , PMap [str , AnchorType ]],
99
+ ] = field (
51
100
default = m (),
52
101
repr = lambda value : f"({ len (value )} entries)" ,
53
102
)
54
103
_uncrawled : PSet [str ] = field (default = s (), repr = False )
55
- _specification : Specification = OpaqueSpecification ()
56
104
57
105
def update (self , * registries : Registry ) -> Registry :
58
106
contents = (each ._contents for each in registries )
@@ -64,28 +112,41 @@ def update(self, *registries: Registry) -> Registry:
64
112
)
65
113
66
114
def with_resource (self , resource : Schema ) -> Registry :
67
- uri = self ._specification .id_of (resource )
68
- if uri is None :
69
- raise UnidentifiedResource (resource )
70
- return self .with_identified_resource (uri = uri , resource = resource )
115
+ identified = IdentifiedResource .from_resource (resource )
116
+ return self .with_identified_resource (
117
+ uri = identified .id (),
118
+ resource = identified ,
119
+ )
71
120
72
- def with_identified_resource (self , uri , resource ) -> Registry :
73
- return self .with_resources ([(uri , resource )])
121
+ def with_resources (
122
+ self ,
123
+ pairs : Iterable [tuple [str , Schema ]],
124
+ ** kwargs ,
125
+ ) -> Registry :
126
+ return self .with_identified_resources (
127
+ (uri , IdentifiedResource .from_resource (resource , ** kwargs ))
128
+ for uri , resource in pairs
129
+ )
130
+
131
+ def with_identified_resource (
132
+ self ,
133
+ uri : str ,
134
+ resource : IdentifiedResource ,
135
+ ) -> Registry :
136
+ return self .with_identified_resources ([(uri , resource )])
74
137
75
- def with_resources (self , pairs : Iterable [tuple [str , Schema ]]) -> Registry :
138
+ def with_identified_resources (
139
+ self ,
140
+ pairs : Iterable [tuple [str , IdentifiedResource ]],
141
+ ) -> Registry :
76
142
uncrawled = self ._uncrawled
77
143
contents = self ._contents
78
144
for uri , resource in pairs :
79
- assert (
80
- uri == ""
81
- or uri not in self ._contents
82
- or self ._contents [uri ][0 ] == resource
83
- ), (uri , self ._contents [uri ], resource )
84
- contents = contents .set (uri , (resource , m ()))
85
-
86
- id = self ._specification .id_of (resource )
145
+ anchors : PMap [str , AnchorType ] = m ()
146
+ contents = contents .set (uri , (resource , anchors ))
147
+ id = resource .id ()
87
148
if id is not None :
88
- contents = contents .set (id , (resource , m () ))
149
+ contents = contents .set (id , (resource , anchors ))
89
150
90
151
uncrawled = uncrawled .add (uri )
91
152
return evolve (self , contents = contents , uncrawled = uncrawled )
@@ -101,7 +162,7 @@ def with_anchors(
101
162
contents = self ._contents .set (uri , (resource , new ))
102
163
return evolve (self , contents = contents )
103
164
104
- def resource_at (self , uri : str ) -> tuple [Schema , Registry ]:
165
+ def resource_at (self , uri : str ) -> tuple [IdentifiedResource , Registry ]:
105
166
at_uri = self ._contents .get (uri )
106
167
if at_uri is not None and at_uri [1 ]:
107
168
registry = self
@@ -114,33 +175,40 @@ def anchors_at(self, uri: str) -> PMap[str, AnchorType]:
114
175
115
176
def _crawl (self ) -> Registry :
116
177
registry = self
117
- resources = [(uri , self ._contents [uri ][0 ]) for uri in self ._uncrawled ]
178
+ resources : list [tuple [str , IdentifiedResource ]] = [
179
+ (uri , self ._contents [uri ][0 ]) for uri in self ._uncrawled
180
+ ]
118
181
while resources :
119
182
base_uri , resource = resources .pop ()
120
- if resource is True or resource is False :
183
+ if resource . resource is True or resource . resource is False :
121
184
continue
122
185
123
- uri = urljoin (base_uri , self . _specification . id_of ( resource ) or "" )
186
+ uri = urljoin (base_uri , resource . id ( ) or "" )
124
187
if uri != base_uri :
125
188
registry = registry .with_identified_resource (
126
189
uri = uri ,
127
190
resource = resource ,
128
191
)
129
192
130
- anchors = self . _specification . anchors_in ( resource )
193
+ anchors = resource . anchors ( )
131
194
registry = registry .with_anchors (uri = uri , anchors = anchors )
132
195
133
196
resources .extend (
134
197
(uri , each )
135
- for each in self . _specification . subresources_of ( resource )
198
+ for each in resource . subresources ( )
136
199
if each is not True and each is not False
137
200
)
138
201
return evolve (registry , uncrawled = s ())
139
202
140
203
def resolver (self , root : Schema , specification : Specification ) -> Resolver :
141
- uri = self ._specification .id_of (root ) or ""
142
- registry = self .with_identified_resource (uri = uri , resource = root )
143
- registry = evolve (registry , specification = specification )
204
+ uri = specification .id_of (root ) or ""
205
+ registry = self .with_identified_resource (
206
+ uri = uri ,
207
+ resource = IdentifiedResource (
208
+ specification = specification ,
209
+ resource = root ,
210
+ ),
211
+ )
144
212
return Resolver (base_uri = uri , registry = registry )
145
213
146
214
@@ -157,8 +225,9 @@ def lookup(self, ref: str) -> tuple[Schema, Resolver]:
157
225
else :
158
226
uri , fragment = urldefrag (urljoin (self ._base_uri , ref ))
159
227
160
- target , registry = self ._registry .resource_at (uri )
228
+ resource , registry = self ._registry .resource_at (uri )
161
229
base_uri = uri
230
+ target = resource .resource
162
231
163
232
if fragment .startswith ("/" ):
164
233
segments = unquote (fragment [1 :]).split ("/" )
@@ -171,31 +240,40 @@ def lookup(self, ref: str) -> tuple[Schema, Resolver]:
171
240
# FIXME: this is wrong, we need to know that we are crossing
172
241
# the boundary of a *schema* specifically
173
242
if not isinstance (target , Sequence ):
174
- id = self . _registry ._specification .id_of (target )
243
+ id = resource ._specification .id_of (target )
175
244
if id is not None :
176
245
base_uri = urljoin (base_uri , id ).rstrip ("#" )
177
246
elif fragment :
178
247
anchor = registry .anchors_at (uri = uri )[fragment ]
179
- target , uri = anchor .resolve (resolver = self , uri = uri )
248
+ resource , uri = anchor .resolve (resolver = self , uri = uri )
249
+ target = resource .resource
180
250
181
- id = self . _registry . _specification . id_of ( target )
251
+ id = resource . id ( )
182
252
if id is not None :
183
253
base_uri = urljoin (self ._base_uri , id ).rstrip ("#" )
184
254
else :
185
- id = self ._registry ._specification .id_of (target )
255
+ target = resource .resource
256
+ id = resource .id ()
186
257
if id is not None :
187
258
base_uri = urljoin (self ._base_uri , id ).rstrip ("#" )
188
259
return target , self .evolve (base_uri = base_uri , registry = registry )
189
260
190
- def with_root (self , root : Schema ) -> Resolver :
191
- maybe_relative = self ._registry ._specification .id_of (root )
261
+ def with_root (
262
+ self ,
263
+ root : Schema ,
264
+ specification : Specification ,
265
+ ) -> Resolver :
266
+ maybe_relative = specification .id_of (root )
192
267
if maybe_relative is None :
193
268
return self
194
269
195
270
uri = urljoin (self ._base_uri , maybe_relative )
196
271
registry = self ._registry .with_identified_resource (
197
272
uri = uri ,
198
- resource = root ,
273
+ resource = IdentifiedResource (
274
+ resource = root ,
275
+ specification = specification ,
276
+ ),
199
277
)
200
278
return self .evolve (base_uri = uri , registry = registry )
201
279
0 commit comments