Skip to content

Commit 520e04a

Browse files
author
Ed (ODSC)
committed
sphinxcontrib/jsonschema.py: Add urn handling
openownership/data-standard#546
1 parent 1ebf6a4 commit 520e04a

File tree

4 files changed

+47
-6
lines changed

4 files changed

+47
-6
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
strategy:
99
matrix:
1010
python-version: [ '3.8', '3.9', '3.10', '3.11']
11-
myst-parser-version: [ '<0.18.0', '>=0.18.0,<0.19', '>=0.19.0,<1.0', '>=1.0.0,<2', '>=2.0.0,<3']
11+
myst-parser-version: [ '>=0.18.0,<0.19', '>=0.19.0,<1.0', '>=1.0.0,<2', '>=2.0.0,<3']
1212
jsonref-version: [">1"]
1313
include:
1414
# jsonref 1.0 has a backwards incompatible change - make sure we test just once with an older version of jsonref

requirements_dev.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
-e .
22
sphinx
3-
flake8
3+
lxml
4+
flake8<6

setup.py

+3
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@
3838
'jsonref',
3939
'jsonpointer',
4040
'myst-parser',
41+
'referencing',
42+
'jscc',
4143
],
4244
extras_require={
4345
'test': [
4446
'flake8<6',
4547
'lxml',
48+
'defusedxml',
4649
'pytest',
4750
],
4851
},

sphinxcontrib/jsonschema.py

+41-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
from docutils import nodes
1414
from docutils.parsers.rst import directives, Directive
1515
from pathlib import Path
16+
from urllib import parse as urlparse
17+
from referencing import Registry, Resource
18+
from referencing.jsonschema import DRAFT202012
19+
from jscc.schema import is_json_schema
20+
from jscc.testing.filesystem import walk_json_data
1621

1722
import json
1823
from collections import OrderedDict
@@ -38,6 +43,26 @@ def custom_jsonref_jsonloader(uri, **kwargs):
3843
return {}
3944

4045

46+
def build_custom_schema_loader(schema_path):
47+
"""
48+
Buildss a callable which handles a URN if provided (e.g. resolves part
49+
before hash in 'urn:components#/$defs/UnspecifiedRecord' to components.json
50+
in schema_path dircetory), else calls default JSON loader.
51+
"""
52+
schemas = []
53+
for _, _, _, data in walk_json_data(top=schema_path):
54+
if is_json_schema(data):
55+
schemas.append((data.get("$id"), Resource(contents=data, specification=DRAFT202012)))
56+
registry = Registry().with_resources(schemas)
57+
def custom_loader(uri, **kwargs):
58+
scheme = urlparse.urlsplit(uri).scheme
59+
if scheme == "urn":
60+
return registry.contents(uri.split("#")[0])
61+
else:
62+
return jsonref.jsonloader(uri, **kwargs)
63+
return custom_loader
64+
65+
4166
class JSONSchemaDirective(Directive):
4267
has_content = True
4368
required_arguments = 1
@@ -49,6 +74,7 @@ class JSONSchemaDirective(Directive):
4974
'addtargets': directives.flag,
5075
'externallinks': directives.unchanged,
5176
'allowexternalrefs': directives.flag,
77+
'allowurnrefs': directives.flag,
5278
}
5379
# Add a rollup option here
5480

@@ -87,11 +113,18 @@ def run(self):
87113
self.arguments[0])
88114
env.note_dependency(relpath)
89115

90-
schema = JSONSchema.loadfromfile(abspath, allow_external_refs=('allowexternalrefs' in self.options))
116+
schema = JSONSchema.loadfromfile(
117+
abspath,
118+
allow_external_refs=('allowexternalrefs' in self.options),
119+
loader=(build_custom_schema_loader(abspath.rsplit("/", 1)[0])
120+
if 'allowurnrefs' in self.options else None)
121+
)
91122
else:
92123
schema = JSONSchema.loadfromfile(
93124
''.join(self.content),
94-
allow_external_refs=('allowexternalrefs' in self.options)
125+
allow_external_refs=('allowexternalrefs' in self.options),
126+
loader=(build_custom_schema_loader(abspath.rsplit("/", 1)[0])
127+
if 'allowurnrefs' in self.options else None)
95128
)
96129
except ValueError as exc:
97130
raise self.error('Failed to parse JSON Schema: %s' % exc)
@@ -242,10 +275,12 @@ def simplify(obj):
242275

243276
class JSONSchema(object):
244277
@classmethod
245-
def load(cls, reader, allow_external_refs=False, base_uri=""):
278+
def load(cls, reader, allow_external_refs=False, base_uri="", loader=None):
246279
args = {}
247280
if not allow_external_refs:
248281
args['loader'] = custom_jsonref_jsonloader
282+
if loader:
283+
args['loader'] = loader
249284
obj = jsonref.load(reader, object_pairs_hook=OrderedDict, base_uri=base_uri, **args)
250285
return cls.instantiate(None, obj)
251286

@@ -255,12 +290,14 @@ def loads(cls, string):
255290
return cls.instantiate(None, obj)
256291

257292
@classmethod
258-
def loadfromfile(cls, filename, allow_external_refs=False):
293+
def loadfromfile(cls, filename, allow_external_refs=False, loader=None):
259294
with io.open(filename, 'rt', encoding='utf-8') as reader:
260295
args = {}
261296
if allow_external_refs:
262297
args['allow_external_refs'] = True
263298
args['base_uri'] = Path(os.path.realpath(filename)).as_uri()
299+
if loader:
300+
args['loader'] = loader
264301
return cls.load(reader, **args)
265302

266303
@classmethod

0 commit comments

Comments
 (0)