Skip to content

Commit 190daa9

Browse files
committed
Add load_urn_schema_filenames
1 parent 69984f8 commit 190daa9

File tree

9 files changed

+194
-1
lines changed

9 files changed

+194
-1
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- URN features
12+
-- To CompileToJsonSchema class: load_urn_schema_filenames option
13+
-- To CLI: --urn-schema-filename option
914

1015
### Removed
1116

compiletojsonschema/cli/__main__.py

+7
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@ def main():
1818
"--codelist-base-directory",
1919
help="Which directory we should look in for codelists",
2020
)
21+
parser.add_argument(
22+
"-u",
23+
"--urn-schema-filename",
24+
help="Filenames of additional schemas to load and refer to by URN later while processing the input file",
25+
action="append",
26+
)
2127

2228
args = parser.parse_args()
2329

2430
ctjs = CompileToJsonSchema(
2531
input_filename=args.input_file,
2632
set_additional_properties_false_everywhere=args.set_additional_properties_false_everywhere,
2733
codelist_base_directory=args.codelist_base_directory,
34+
load_urn_schema_filenames=args.urn_schema_filename or [],
2835
)
2936
print(ctjs.get_as_string())

compiletojsonschema/compiletojsonschema.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
import csv
2+
import functools
23
import json
34
import os
45
import pathlib
56
from collections import OrderedDict
67
from copy import deepcopy
78

89
import jsonref
10+
import referencing
11+
12+
13+
def _jsonref_loader(uri, urn_registry=None):
14+
if uri.startswith("urn:"):
15+
if urn_registry and uri in urn_registry:
16+
return urn_registry.contents(uri)
17+
else:
18+
raise Exception("URN {} not found".format(uri))
19+
return jsonref.jsonloader(uri)
920

1021

1122
class CompileToJsonSchema:
@@ -15,6 +26,7 @@ def __init__(
1526
set_additional_properties_false_everywhere=False,
1627
codelist_base_directory=None,
1728
input_schema=None,
29+
load_urn_schema_filenames=[],
1830
):
1931
if not isinstance(input_schema, dict) and not input_filename:
2032
raise Exception("Must pass input_filename or input_schema")
@@ -27,19 +39,35 @@ def __init__(
2739
self.codelist_base_directory = os.path.expanduser(codelist_base_directory)
2840
else:
2941
self.codelist_base_directory = os.getcwd()
42+
self.load_urn_schema_filenames = load_urn_schema_filenames
3043

3144
def get(self):
45+
urn_registry = None
46+
if self.load_urn_schema_filenames:
47+
urn_registry = referencing.Registry()
48+
for urn_schema_filename in self.load_urn_schema_filenames:
49+
with open(urn_schema_filename) as fp:
50+
urn_schema_json = json.load(fp)
51+
urn_schema_obj = referencing.Resource.from_contents(urn_schema_json)
52+
urn_registry = urn_schema_obj @ urn_registry
53+
3254
if self.input_filename:
3355
with open(self.input_filename) as fp:
3456
resolved = jsonref.load(
3557
fp,
58+
loader=functools.partial(
59+
_jsonref_loader, urn_registry=urn_registry
60+
),
3661
object_pairs_hook=OrderedDict,
3762
base_uri=pathlib.Path(
3863
os.path.realpath(self.input_filename)
3964
).as_uri(),
4065
)
4166
elif isinstance(self.input_schema, dict):
42-
resolved = jsonref.JsonRef.replace_refs(self.input_schema)
67+
resolved = jsonref.JsonRef.replace_refs(
68+
self.input_schema,
69+
loader=functools.partial(_jsonref_loader, urn_registry=urn_registry),
70+
)
4371
else:
4472
raise Exception("Must pass input_filename or input_schema")
4573

docs/cli.rst

+12
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,15 @@ To enable this mode, pass the `--set-additional-properties-false-everywhere` or
3737
3838
compiletojsonschema -s input.json
3939
compiletojsonschema --set-additional-properties-false-everywhere input.json
40+
41+
42+
URN Refs
43+
--------
44+
45+
Pass the names of files to load. This can be used multiple times.
46+
47+
.. code-block:: shell-session
48+
49+
compiletojsonschema -u library.json -u components.json schema.json
50+
compiletojsonschema --urn-schema-filename library.json --urn-schema-filename components.json schema.json
51+

docs/features.rst

+49
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,52 @@ you may want to generate a strict version of your schema that doesn't allow any
155155
This can be used for testing - for example, checking your sample data does not have any additional properties.
156156

157157
This is an optional mode, which defaults to off.
158+
159+
URN Refs
160+
--------
161+
162+
You can use URN's in refs.
163+
164+
For example, if you have the `library.json` schema:
165+
166+
.. code-block:: json
167+
168+
{
169+
"$id": "urn:library",
170+
"$schema": "https://json-schema.org/draft/2020-12/schema",
171+
"$defs": {
172+
"address": {
173+
"type": "object",
174+
"properties": {
175+
"address": {
176+
"type": "string"
177+
}
178+
}
179+
}
180+
}
181+
}
182+
183+
And a schema `schema.json`:
184+
185+
.. code-block:: json
186+
187+
{
188+
"$id": "urn:schema",
189+
"$schema": "https://json-schema.org/draft/2020-12/schema",
190+
"properties": {
191+
"home_address": {
192+
"$ref": "urn:library#/$defs/address"
193+
},
194+
"work_address": {
195+
"$ref": "urn:library#/$defs/address"
196+
}
197+
}
198+
}
199+
200+
This will compile.
201+
202+
To do so:
203+
204+
* you should pass the actual schema file `schema.json` to the tool where processing should start.
205+
* you will need to make sure the tool knows about the other `library.json` file.
206+

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
install_requires=[
1616
"jsonref",
1717
"jsonschema",
18+
"referencing",
1819
],
1920
extras_require={
2021
"test": extras_require_test,

tests/fixtures/urn/library.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"$id": "urn:library",
3+
"$schema": "https://json-schema.org/draft/2020-12/schema",
4+
"$defs": {
5+
"address": {
6+
"title": "An Address",
7+
"description": "A Description",
8+
"type": "object",
9+
"properties": {
10+
"address": {
11+
"type": "string"
12+
},
13+
"country": {
14+
"type": "string"
15+
}
16+
}
17+
}
18+
}
19+
}

tests/fixtures/urn/schema.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$id": "urn:schema",
3+
"$schema": "https://json-schema.org/draft/2020-12/schema",
4+
"properties": {
5+
"home_address": {
6+
"title": "Home Address",
7+
"description": "Where the person lives",
8+
"$ref": "urn:library#/$defs/address"
9+
},
10+
"work_address": {
11+
"title": "Work Address",
12+
"description": "Where the person works",
13+
"$ref": "urn:library#/$defs/address"
14+
}
15+
}
16+
}

tests/test_urn.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import json
2+
import os
3+
4+
import jsonref
5+
import pytest
6+
7+
from compiletojsonschema.compiletojsonschema import CompileToJsonSchema
8+
9+
10+
def test_urn():
11+
12+
input_filename = os.path.join(
13+
os.path.dirname(os.path.realpath(__file__)),
14+
"fixtures",
15+
"urn",
16+
"schema.json",
17+
)
18+
19+
lib_filename = os.path.join(
20+
os.path.dirname(os.path.realpath(__file__)),
21+
"fixtures",
22+
"urn",
23+
"library.json",
24+
)
25+
26+
ctjs = CompileToJsonSchema(
27+
input_filename=input_filename, load_urn_schema_filenames=[lib_filename]
28+
)
29+
out_string = ctjs.get_as_string()
30+
out = json.loads(out_string)
31+
32+
assert out["properties"]["work_address"]["title"] == "Work Address"
33+
assert out["properties"]["work_address"]["description"] == "Where the person works"
34+
assert (
35+
out["properties"]["work_address"]["properties"]["address"]["type"] == "string"
36+
)
37+
assert out["properties"]["home_address"]["title"] == "Home Address"
38+
assert out["properties"]["home_address"]["description"] == "Where the person lives"
39+
assert (
40+
out["properties"]["home_address"]["properties"]["address"]["type"] == "string"
41+
)
42+
43+
44+
def test_urn_not_loaded():
45+
46+
input_filename = os.path.join(
47+
os.path.dirname(os.path.realpath(__file__)),
48+
"fixtures",
49+
"urn",
50+
"schema.json",
51+
)
52+
53+
ctjs = CompileToJsonSchema(input_filename=input_filename)
54+
55+
with pytest.raises(jsonref.JsonRefError):
56+
ctjs.get_as_string()

0 commit comments

Comments
 (0)