Skip to content

Add URN #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- URN features
-- To CompileToJsonSchema class: load_urn_schema_filenames option
-- To CLI: --urn-schema-filename option

### Removed

Expand Down
7 changes: 7 additions & 0 deletions compiletojsonschema/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ def main():
"--codelist-base-directory",
help="Which directory we should look in for codelists",
)
parser.add_argument(
"-u",
"--urn-schema-filename",
help="Filenames of additional schemas to load and refer to by URN later while processing the input file",
action="append",
)

args = parser.parse_args()

ctjs = CompileToJsonSchema(
input_filename=args.input_file,
set_additional_properties_false_everywhere=args.set_additional_properties_false_everywhere,
codelist_base_directory=args.codelist_base_directory,
load_urn_schema_filenames=args.urn_schema_filename or [],
)
print(ctjs.get_as_string())
30 changes: 29 additions & 1 deletion compiletojsonschema/compiletojsonschema.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import csv
import functools
import json
import os
import pathlib
from collections import OrderedDict
from copy import deepcopy

import jsonref
import referencing


def _jsonref_loader(uri, urn_registry=None):
if uri.startswith("urn:"):
if urn_registry and uri in urn_registry:
return urn_registry.contents(uri)
else:
raise Exception("URN {} not found".format(uri))
return jsonref.jsonloader(uri)


class CompileToJsonSchema:
Expand All @@ -15,6 +26,7 @@ def __init__(
set_additional_properties_false_everywhere=False,
codelist_base_directory=None,
input_schema=None,
load_urn_schema_filenames=[],
):
if not isinstance(input_schema, dict) and not input_filename:
raise Exception("Must pass input_filename or input_schema")
Expand All @@ -27,19 +39,35 @@ def __init__(
self.codelist_base_directory = os.path.expanduser(codelist_base_directory)
else:
self.codelist_base_directory = os.getcwd()
self.load_urn_schema_filenames = load_urn_schema_filenames

def get(self):
urn_registry = None
if self.load_urn_schema_filenames:
urn_registry = referencing.Registry()
for urn_schema_filename in self.load_urn_schema_filenames:
with open(urn_schema_filename) as fp:
urn_schema_json = json.load(fp)
urn_schema_obj = referencing.Resource.from_contents(urn_schema_json)
urn_registry = urn_schema_obj @ urn_registry

if self.input_filename:
with open(self.input_filename) as fp:
resolved = jsonref.load(
fp,
loader=functools.partial(
_jsonref_loader, urn_registry=urn_registry
),
object_pairs_hook=OrderedDict,
base_uri=pathlib.Path(
os.path.realpath(self.input_filename)
).as_uri(),
)
elif isinstance(self.input_schema, dict):
resolved = jsonref.JsonRef.replace_refs(self.input_schema)
resolved = jsonref.JsonRef.replace_refs(
self.input_schema,
loader=functools.partial(_jsonref_loader, urn_registry=urn_registry),
)
else:
raise Exception("Must pass input_filename or input_schema")

Expand Down
12 changes: 12 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,15 @@ To enable this mode, pass the `--set-additional-properties-false-everywhere` or

compiletojsonschema -s input.json
compiletojsonschema --set-additional-properties-false-everywhere input.json


URN Refs
--------

Pass the names of files to load. This can be used multiple times.

.. code-block:: shell-session

compiletojsonschema -u library.json -u components.json schema.json
compiletojsonschema --urn-schema-filename library.json --urn-schema-filename components.json schema.json

49 changes: 49 additions & 0 deletions docs/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,52 @@ you may want to generate a strict version of your schema that doesn't allow any
This can be used for testing - for example, checking your sample data does not have any additional properties.

This is an optional mode, which defaults to off.

URN Refs
--------

You can use URN's in refs.

For example, if you have the `library.json` schema:

.. code-block:: json

{
"$id": "urn:library",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"address": {
"type": "object",
"properties": {
"address": {
"type": "string"
}
}
}
}
}

And a schema `schema.json`:

.. code-block:: json

{
"$id": "urn:schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"home_address": {
"$ref": "urn:library#/$defs/address"
},
"work_address": {
"$ref": "urn:library#/$defs/address"
}
}
}

This will compile.

To do so:

* you should pass the actual schema file `schema.json` to the tool where processing should start.
* you will need to make sure the tool knows about the other `library.json` file.

1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
install_requires=[
"jsonref",
"jsonschema",
"referencing",
],
extras_require={
"test": extras_require_test,
Expand Down
19 changes: 19 additions & 0 deletions tests/fixtures/urn/library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$id": "urn:library",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"address": {
"title": "An Address",
"description": "A Description",
"type": "object",
"properties": {
"address": {
"type": "string"
},
"country": {
"type": "string"
}
}
}
}
}
16 changes: 16 additions & 0 deletions tests/fixtures/urn/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$id": "urn:schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"home_address": {
"title": "Home Address",
"description": "Where the person lives",
"$ref": "urn:library#/$defs/address"
},
"work_address": {
"title": "Work Address",
"description": "Where the person works",
"$ref": "urn:library#/$defs/address"
}
}
}
56 changes: 56 additions & 0 deletions tests/test_urn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import json
import os

import jsonref
import pytest

from compiletojsonschema.compiletojsonschema import CompileToJsonSchema


def test_urn():

input_filename = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"fixtures",
"urn",
"schema.json",
)

lib_filename = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"fixtures",
"urn",
"library.json",
)

ctjs = CompileToJsonSchema(
input_filename=input_filename, load_urn_schema_filenames=[lib_filename]
)
out_string = ctjs.get_as_string()
out = json.loads(out_string)

assert out["properties"]["work_address"]["title"] == "Work Address"
assert out["properties"]["work_address"]["description"] == "Where the person works"
assert (
out["properties"]["work_address"]["properties"]["address"]["type"] == "string"
)
assert out["properties"]["home_address"]["title"] == "Home Address"
assert out["properties"]["home_address"]["description"] == "Where the person lives"
assert (
out["properties"]["home_address"]["properties"]["address"]["type"] == "string"
)


def test_urn_not_loaded():

input_filename = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"fixtures",
"urn",
"schema.json",
)

ctjs = CompileToJsonSchema(input_filename=input_filename)

with pytest.raises(jsonref.JsonRefError):
ctjs.get_as_string()