1
1
import json
2
+ import re
3
+ from functools import cached_property
2
4
from pathlib import Path
3
5
from uuid import uuid4
4
6
from typing import Any , Optional
10
12
from rdflib .namespace import RDF
11
13
import yaml
12
14
15
+ from oascomply .pointers import (
16
+ RelativeJSONPointerTemplate ,
17
+ RelativeJSONPointerTemplateError ,
18
+ )
19
+
13
20
__all__ = [
14
21
'OasGraph' ,
15
22
]
49
56
50
57
51
58
class OasGraph :
52
- def __init__ (self , version ):
59
+ """
60
+ Graph representing an OAS API description
61
+
62
+ :param version: The X.Y OAS version string for the description
63
+ """
64
+ def __init__ (self , version : str ):
53
65
if version not in ('3.0' , '3.1' ):
54
66
raise ValueError (f'OAS v{ version } is not supported.' )
55
67
if version == '3.1' :
56
68
raise ValueError (f'OAS v3.1 support TBD.' )
69
+ self ._version = version
57
70
58
71
self ._g = rdflib .Graph ()
59
- self ._oas = rdflib .Namespace (
60
- f 'https://spec.openapis.org/oas/v { version } /ontology#'
72
+ self ._oas_unversioned = rdflib .Namespace (
73
+ 'https://spec.openapis.org/compliance /ontology#'
61
74
)
62
- self ._g .bind ('oas3.0' , self ._oas )
75
+ self ._oas_versions = {
76
+ '3.0' : rdflib .Namespace (
77
+ 'https://spec.openapis.org/compliance/ontology#3.0-'
78
+ ),
79
+ '3.1' : rdflib .Namespace (
80
+ 'https://spec.openapis.org/compliance/ontology#3.1-'
81
+ ),
82
+ }
83
+ self ._g .bind ('oas' , self ._oas_unversioned )
84
+ self ._g .bind ('oas3.0' , self ._oas_versions ['3.0' ])
85
+ self ._g .bind ('oas3.1' , self ._oas_versions ['3.1' ])
86
+
87
+ @cached_property
88
+ def oas (self ):
89
+ return self ._oas_unversioned
90
+
91
+ @cached_property
92
+ def oas_v (self ):
93
+ return self ._oas_versions [self ._version ]
63
94
64
95
def serialize (self , * args , base = None , output_format = None , ** kwargs ):
65
96
"""Serialize the graph using the given output format."""
@@ -77,12 +108,17 @@ def add_resource(self, location, iri):
77
108
rdf_node = rdflib .URIRef (iri )
78
109
self ._g .add ((
79
110
rdf_node ,
80
- self ._oas ['locatedAt' ],
111
+ self .oas ['locatedAt' ],
81
112
rdflib .URIRef (
82
113
location .resolve ().as_uri () if isinstance (location , Path )
83
114
else location ,
84
115
),
85
116
))
117
+ self ._g .add ((
118
+ rdf_node ,
119
+ self .oas ['root' ],
120
+ rdf_node + '#' ,
121
+ ))
86
122
filename = None
87
123
if isinstance (location , Path ):
88
124
filename = location .name
@@ -94,7 +130,7 @@ def add_resource(self, location, iri):
94
130
if filename :
95
131
self ._g .add ((
96
132
rdf_node ,
97
- self ._oas ['filename' ],
133
+ self .oas ['filename' ],
98
134
rdflib .Literal (filename ),
99
135
))
100
136
@@ -104,14 +140,14 @@ def add_oastype(self, annotation, instance, sourcemap):
104
140
self ._g .add ((
105
141
instance_uri ,
106
142
RDF .type ,
107
- self ._oas [annotation .value ],
108
- ))
109
- self ._g .add ((
110
- instance_uri ,
111
- RDF .type ,
112
- self ._oas ['ParsedStructure' ],
143
+ self .oas_v [annotation .value ],
113
144
))
114
145
if sourcemap :
146
+ self ._g .add ((
147
+ instance_uri ,
148
+ RDF .type ,
149
+ self .oas ['ParsedStructure' ],
150
+ ))
115
151
self .add_sourcemap (
116
152
instance_uri ,
117
153
annotation .location .instance_ptr ,
@@ -126,28 +162,55 @@ def add_sourcemap(self, instance_rdf_uri, instance_ptr, sourcemap):
126
162
entry = sourcemap [map_key ]
127
163
self ._g .add ((
128
164
instance_rdf_uri ,
129
- self ._oas ['line' ],
165
+ self .oas ['line' ],
130
166
rdflib .Literal (entry .value_start .line ),
131
167
))
132
168
self ._g .add ((
133
169
instance_rdf_uri ,
134
- self ._oas ['column' ],
170
+ self .oas ['column' ],
135
171
rdflib .Literal (entry .value_start .column ),
136
172
))
137
173
174
+ def _resolve_child_template (
175
+ self ,
176
+ annotation ,
177
+ instance ,
178
+ value_processor = None ,
179
+ ):
180
+ parent_uri = rdflib .URIRef (str (annotation .location .instance_uri ))
181
+ parent_obj = annotation .location .instance_ptr .evaluate (instance )
182
+ for child_template , ann_value in annotation .value .items ():
183
+ # Yield back relname unchanged?
184
+ # Take modifier funciton?
185
+ # double generator of some sort?
186
+ rdf_name = ann_value .value # unwrap jschon.JSON
187
+ relptr = None
188
+ if re .match (r'\d' , rdf_name ):
189
+ relptr = jschon .RelativeJSONPointer (rdf_name )
190
+ rdf_name = None
191
+
192
+ yield from (
193
+ (
194
+ result ,
195
+ rdf_name if rdf_name
196
+ else relptr .evaluate (result .data ),
197
+ )
198
+ for result in RelativeJSONPointerTemplate (
199
+ child_template ,
200
+ ).evaluate (parent_obj )
201
+ )
202
+
138
203
def add_oaschildren (self , annotation , instance , sourcemap ):
139
204
location = annotation .location
140
205
# to_rdf()
141
206
parent_uri = rdflib .URIRef (str (location .instance_uri ))
142
- for child in annotation .value :
143
- child = child .value
144
- if '{' in child :
145
- continue
146
-
147
- child_ptr = jschon .RelativeJSONPointer (child )
148
- parent_obj = location .instance_ptr .evaluate (instance )
149
- try :
150
- child_obj = child_ptr .evaluate (parent_obj )
207
+ parent_obj = location .instance_ptr .evaluate (instance )
208
+ try :
209
+ for result , relname in self ._resolve_child_template (
210
+ annotation ,
211
+ instance ,
212
+ ):
213
+ child_obj = result .data
151
214
child_path = child_obj .path
152
215
iu = location .instance_uri
153
216
# replace fragment; to_rdf
@@ -156,12 +219,12 @@ def add_oaschildren(self, annotation, instance, sourcemap):
156
219
)))
157
220
self ._g .add ((
158
221
parent_uri ,
159
- self ._oas [ child_ptr . path [ 0 ] ],
222
+ self .oas [ relname ],
160
223
child_uri ,
161
224
))
162
225
self ._g .add ((
163
226
child_uri ,
164
- self ._oas ['parent' ],
227
+ self .oas ['parent' ],
165
228
parent_uri ,
166
229
))
167
230
if sourcemap :
@@ -170,20 +233,52 @@ def add_oaschildren(self, annotation, instance, sourcemap):
170
233
child_path ,
171
234
sourcemap ,
172
235
)
173
- except jschon .RelativeJSONPointerError as e :
174
- pass
236
+ except (
237
+ jschon .RelativeJSONPointerError ,
238
+ RelativeJSONPointerTemplateError ,
239
+ ) as e :
240
+ # TODO: actual error handling
241
+ raise
242
+
243
+ def add_oasliterals (self , annotation , instance , sourcemap ):
244
+ location = annotation .location
245
+ # to_rdf()
246
+ parent_uri = rdflib .URIRef (str (location .instance_uri ))
247
+ parent_obj = location .instance_ptr .evaluate (instance )
248
+ try :
249
+ for result , relname in self ._resolve_child_template (
250
+ annotation ,
251
+ instance ,
252
+ ):
253
+ literal = result .data
254
+ literal_path = literal .path
255
+ iu = location .instance_uri
256
+ # replace fragment; to_rdf
257
+ literal_node = rdflib .Literal (literal .value )
258
+ self ._g .add ((
259
+ parent_uri ,
260
+ self .oas [relname ],
261
+ literal_node ,
262
+ ))
263
+ # TODO: Sourcemap for literals? might need
264
+ # intermediate node as literals cannot
265
+ # be subjects in triples.
266
+ except (
267
+ jschon .RelativeJSONPointerError ,
268
+ RelativeJSONPointerTemplateError ,
269
+ ) as e :
270
+ # TODO: actual error handling
271
+ raise
175
272
176
273
def add_oasreferences (self , annotation , instance , sourcemap ):
177
274
location = annotation .location
178
275
remote_resources = []
179
- for refloc , reftype in annotation .value .items ():
180
- reftype = reftype .value
181
- # if '{' in refloc:
182
- # continue
183
- try :
184
- ref_ptr = jschon .RelativeJSONPointer (refloc )
185
- parent_obj = location .instance_ptr .evaluate (instance )
186
- ref_obj = ref_ptr .evaluate (parent_obj )
276
+ try :
277
+ for template_result , reftype in self ._resolve_child_template (
278
+ annotation ,
279
+ instance ,
280
+ ):
281
+ ref_obj = template_result .data
187
282
ref_source_path = ref_obj .path
188
283
iu = location .instance_uri
189
284
# replace fragment; to_rdf
@@ -195,7 +290,7 @@ def add_oasreferences(self, annotation, instance, sourcemap):
195
290
))
196
291
self ._g .add ((
197
292
ref_src_uri ,
198
- self ._oas ['references' ],
293
+ self .oas ['references' ],
199
294
ref_target_uri ,
200
295
))
201
296
# TODO: elide the reference with a new edge
@@ -213,6 +308,12 @@ def add_oasreferences(self, annotation, instance, sourcemap):
213
308
ref_source_path ,
214
309
sourcemap ,
215
310
)
216
- except (ValueError , jschon .RelativeJSONPointerError ) as e :
217
- pass
311
+ except (
312
+ ValueError ,
313
+ jschon .RelativeJSONPointerError ,
314
+ RelativeJSONPointerTemplateError ,
315
+ ) as e :
316
+ # TODO: Actual error handling
317
+ pass
318
+
218
319
return remote_resources
0 commit comments