Skip to content

Commit e85baf1

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

File tree

4 files changed

+48
-6
lines changed

4 files changed

+48
-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', # Not directly used, but require because of issue with sphinx.testing.fixtures plugin
4649
'pytest',
4750
],
4851
},

sphinxcontrib/jsonschema.py

+42-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,27 @@ 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+
58+
def custom_loader(uri, **kwargs):
59+
scheme = urlparse.urlsplit(uri).scheme
60+
if scheme == "urn":
61+
return registry.contents(uri.split("#")[0])
62+
else:
63+
return jsonref.jsonloader(uri, **kwargs)
64+
return custom_loader
65+
66+
4167
class JSONSchemaDirective(Directive):
4268
has_content = True
4369
required_arguments = 1
@@ -49,6 +75,7 @@ class JSONSchemaDirective(Directive):
4975
'addtargets': directives.flag,
5076
'externallinks': directives.unchanged,
5177
'allowexternalrefs': directives.flag,
78+
'allowurnrefs': directives.flag,
5279
}
5380
# Add a rollup option here
5481

@@ -87,11 +114,18 @@ def run(self):
87114
self.arguments[0])
88115
env.note_dependency(relpath)
89116

90-
schema = JSONSchema.loadfromfile(abspath, allow_external_refs=('allowexternalrefs' in self.options))
117+
schema = JSONSchema.loadfromfile(
118+
abspath,
119+
allow_external_refs=('allowexternalrefs' in self.options),
120+
loader=(build_custom_schema_loader(abspath.rsplit("/", 1)[0])
121+
if 'allowurnrefs' in self.options else None)
122+
)
91123
else:
92124
schema = JSONSchema.loadfromfile(
93125
''.join(self.content),
94-
allow_external_refs=('allowexternalrefs' in self.options)
126+
allow_external_refs=('allowexternalrefs' in self.options),
127+
loader=(build_custom_schema_loader(abspath.rsplit("/", 1)[0])
128+
if 'allowurnrefs' in self.options else None)
95129
)
96130
except ValueError as exc:
97131
raise self.error('Failed to parse JSON Schema: %s' % exc)
@@ -242,10 +276,12 @@ def simplify(obj):
242276

243277
class JSONSchema(object):
244278
@classmethod
245-
def load(cls, reader, allow_external_refs=False, base_uri=""):
279+
def load(cls, reader, allow_external_refs=False, base_uri="", loader=None):
246280
args = {}
247281
if not allow_external_refs:
248282
args['loader'] = custom_jsonref_jsonloader
283+
if loader:
284+
args['loader'] = loader
249285
obj = jsonref.load(reader, object_pairs_hook=OrderedDict, base_uri=base_uri, **args)
250286
return cls.instantiate(None, obj)
251287

@@ -255,12 +291,14 @@ def loads(cls, string):
255291
return cls.instantiate(None, obj)
256292

257293
@classmethod
258-
def loadfromfile(cls, filename, allow_external_refs=False):
294+
def loadfromfile(cls, filename, allow_external_refs=False, loader=None):
259295
with io.open(filename, 'rt', encoding='utf-8') as reader:
260296
args = {}
261297
if allow_external_refs:
262298
args['allow_external_refs'] = True
263299
args['base_uri'] = Path(os.path.realpath(filename)).as_uri()
300+
if loader:
301+
args['loader'] = loader
264302
return cls.load(reader, **args)
265303

266304
@classmethod

0 commit comments

Comments
 (0)