From c333bbebf728b93ab9b6d938903d6e5d0b7ba0aa Mon Sep 17 00:00:00 2001 From: goei <JsXLRu> Date: Thu, 23 Jul 2020 19:56:59 +0200 Subject: [PATCH] TMSS-272: Use RefResolver to use local refernce to common part, pointing, of the tmss schema --- LCS/PyCommon/json_utils.py | 84 +------------ SAS/TMSS/src/tmss/tmssapp/adapters/parset.py | 2 +- SAS/TMSS/src/tmss/tmssapp/populate.py | 2 +- SAS/TMSS/src/tmss/tmssapp/schemas/base.json | 8 +- .../task-observation-with-stations.json | 4 +- SAS/TMSS/src/tmss/tmssapp/subtasks.py | 4 +- SAS/TMSS/src/tmss/tmssapp/validation.py | 115 +++++++++++++++++- SAS/TMSS/src/tmss/tmssapp/views.py | 2 +- .../src/tmss/tmssapp/viewsets/scheduling.py | 2 +- .../tmss/tmssapp/viewsets/specification.py | 2 +- SAS/TMSS/test/t_parset_adapter.py | 2 +- SAS/TMSS/test/tmss_test_data_django_models.py | 2 +- SAS/TMSS/test/tmss_test_data_rest.py | 2 +- 13 files changed, 131 insertions(+), 100 deletions(-) diff --git a/LCS/PyCommon/json_utils.py b/LCS/PyCommon/json_utils.py index 801e694c143..553b3ff1495 100644 --- a/LCS/PyCommon/json_utils.py +++ b/LCS/PyCommon/json_utils.py @@ -15,10 +15,9 @@ # You should have received a copy of the GNU General Public License along # with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. -from jsonschema import validators, Draft6Validator, RefResolver +from jsonschema import validators, Draft6Validator from copy import deepcopy -import os -import json + def _extend_with_default(validator_class): """ @@ -78,89 +77,18 @@ def get_default_json_object_for_schema(schema: str) -> dict: return add_defaults_to_json_object_for_schema({}, schema) -def add_defaults_to_json_object_for_schema(json_object: dict, schema: str) -> dict: - '''return a copy of the json object with defaults filled in accoring to the schema for all the missing properties''' +def add_defaults_to_json_object_for_schema(json_object: dict, schema: str, ref_resolver=None) -> dict: + '''return a copy of the json object with defaults filled in according to the schema for all the missing properties''' copy_of_json_object = deepcopy(json_object) - if check_ref_in_schema(schema): - resolver = create_local_ref_resolver(BASE_DIR + "/sas/tmss/tmss/tmssapp/schemas/", "base.json") - _DefaultValidatingDraft6Validator(schema=schema, resolver=resolver).validate(copy_of_json_object) + if ref_resolver is not None: + _DefaultValidatingDraft6Validator(schema=schema, resolver=ref_resolver).validate(copy_of_json_object) else: _DefaultValidatingDraft6Validator(schema).validate(copy_of_json_object) return copy_of_json_object -def validate_only(json_object, schema): - if check_ref_in_schema(schema): - resolver = create_local_ref_resolver(BASE_DIR + "/sas/tmss/tmss/tmssapp/schemas/", "base.json") - # hmm the validation will run the schema of the 'base' not the complete.....zucht - print("validate with resolver") - Draft6Validator(schema=schema, resolver=resolver).validate(json_object) - else: - Draft6Validator(schema=schema).validate(json_object) - - -def item_generator(json_input, lookup_key): - if isinstance(json_input, dict): - for k, v in json_input.items(): - if k == lookup_key: - yield v - else: - yield from item_generator(v, lookup_key) - elif isinstance(json_input, list): - for item in json_input: - yield from item_generator(item, lookup_key) -def check_ref_in_schema(schema): - """ - Stupid lazy method for now to determine if there is a reference in the schema - :param schema: - :return: - """ - # print("check") - # if len(list(item_generator(schema, "$ref"))) > 0: - # return True - # else: - # return False - ref_to_base = False - for ref in list(item_generator(schema, "$ref")): - if "base.json" in ref: - ref_to_base = True - print("Is there a reference to base ", ref_to_base) - return ref_to_base - - -def create_local_ref_resolver(abs_path_to_schema, schema_file_name): - """ - Create jsonschema.RefResolver class for all schemas located in given path. - Apparently if you using jsonschema you have to set a reference resolver to referred schemas - for local files: - https://stackoverflow.com/questions/25145160/json-schema-ref-does-not-work-for-relative-path - https://stackoverflow.com/questions/53968770/how-to-set-up-local-file-references-in-python-jsonschema-document - https://stackoverflow.com/questions/42159346/jsonschema-refresolver-to-resolve-multiple-refs-in-python - :param abs_path_to_schema: Absolute path to location of all schemas - :return resolver_object: RefResolver object - """ - schema_store = {} - with open(os.path.join(abs_path_to_schema, schema_file_name), 'r') as fp: - def_schema = json.load(fp) - - file_name = os.listdir(abs_path_to_schema) - for file_name in file_name: - file_full_path = os.path.join(abs_path_to_schema, file_name) - if file_full_path.endswith(".json"): - with open(file_full_path, "r") as fp: - schema = json.load(fp) - if "id" in schema: - schema_store["$id"] = schema - print(schema_store) - resolver = RefResolver.from_schema(def_schema, store=schema_store) - print("Created resolver with schema_store for %s" % schema_file_name) - return resolver - -# First hack is to check if I can load the schema task-calibrator-addon.json which refs to definitions.json -# /home/goei/lofar/build/gnucxx11_opt/installed/lib/python3.6/site-packages/lofar/sas/tmss/tmss/tmssapp/schemas/scheduling-unit.json -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/SAS/TMSS/src/tmss/tmssapp/adapters/parset.py b/SAS/TMSS/src/tmss/tmssapp/adapters/parset.py index 64ae86d3cdb..533c8eab83c 100644 --- a/SAS/TMSS/src/tmss/tmssapp/adapters/parset.py +++ b/SAS/TMSS/src/tmss/tmssapp/adapters/parset.py @@ -20,7 +20,7 @@ from lofar.sas.tmss.tmss.tmssapp import models from lofar.parameterset import parameterset from lofar.common.datetimeutils import formatDatetime -from lofar.common.json_utils import add_defaults_to_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import add_defaults_to_json_object_for_schema from lofar.sas.tmss.tmss.exceptions import * from datetime import datetime diff --git a/SAS/TMSS/src/tmss/tmssapp/populate.py b/SAS/TMSS/src/tmss/tmssapp/populate.py index 951a60718bc..8d0a34bb992 100644 --- a/SAS/TMSS/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/populate.py @@ -23,7 +23,7 @@ from datetime import datetime, timezone from lofar.sas.tmss.tmss.tmssapp import models from lofar.sas.tmss.tmss.tmssapp.models.specification import * from lofar.sas.tmss.tmss.tmssapp.models.scheduling import * -from lofar.common.json_utils import * +from lofar.sas.tmss.tmss.tmssapp.validation import get_default_json_object_for_schema from lofar.common import isTestEnvironment, isDevelopmentEnvironment working_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/SAS/TMSS/src/tmss/tmssapp/schemas/base.json b/SAS/TMSS/src/tmss/tmssapp/schemas/base.json index 4ea9040d1e6..30d593ef7ab 100644 --- a/SAS/TMSS/src/tmss/tmssapp/schemas/base.json +++ b/SAS/TMSS/src/tmss/tmssapp/schemas/base.json @@ -1,5 +1,5 @@ { - "$id": "definitions.json", + "$id": "base.json", "definitions": { "pointing": { "type": "object", @@ -38,11 +38,7 @@ "description": "Second angle [rad] (e.g. DEC)", "default": 22 } - }, - "required": [ - "angle1", - "angle2" - ] + } } } } \ No newline at end of file diff --git a/SAS/TMSS/src/tmss/tmssapp/schemas/task-observation-with-stations.json b/SAS/TMSS/src/tmss/tmssapp/schemas/task-observation-with-stations.json index 2d75067369c..9ca6c62edb5 100644 --- a/SAS/TMSS/src/tmss/tmssapp/schemas/task-observation-with-stations.json +++ b/SAS/TMSS/src/tmss/tmssapp/schemas/task-observation-with-stations.json @@ -155,7 +155,7 @@ "tile_beam": { "title": "Tile beam", "description": "HBA only", - "$ref": "base.json#/definitions/pointing" + "$ref": "base.json#" }, "SAPs": { "type": "array", @@ -179,7 +179,7 @@ "digital_pointing": { "title": "Digital pointing", "default": {}, - "$ref": "#/definitions/pointing" + "$ref": "base.json#/definitions/pointing" }, "subbands": { "type": "array", diff --git a/SAS/TMSS/src/tmss/tmssapp/subtasks.py b/SAS/TMSS/src/tmss/tmssapp/subtasks.py index 361de1d280d..2aa92edfa1a 100644 --- a/SAS/TMSS/src/tmss/tmssapp/subtasks.py +++ b/SAS/TMSS/src/tmss/tmssapp/subtasks.py @@ -6,14 +6,14 @@ from collections.abc import Iterable from lofar.common.datetimeutils import formatDatetime from lofar.common import isProductionEnvironment -from lofar.common.json_utils import add_defaults_to_json_object_for_schema, get_default_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import add_defaults_to_json_object_for_schema, get_default_json_object_for_schema from lofar.common.lcu_utils import get_current_stations from lofar.sas.tmss.tmss.exceptions import SubtaskCreationException, SubtaskSchedulingException from datetime import datetime, timedelta from lofar.common.datetimeutils import parseDatetime -from lofar.common.json_utils import add_defaults_to_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import add_defaults_to_json_object_for_schema from lofar.sas.tmss.tmss.tmssapp.models import * from lofar.sas.resourceassignment.resourceassigner.rarpc import RARPC from lofar.sas.resourceassignment.resourceassignmentservice.rpc import RADBRPC diff --git a/SAS/TMSS/src/tmss/tmssapp/validation.py b/SAS/TMSS/src/tmss/tmssapp/validation.py index d2ba9dc573d..21785954c63 100644 --- a/SAS/TMSS/src/tmss/tmssapp/validation.py +++ b/SAS/TMSS/src/tmss/tmssapp/validation.py @@ -1,7 +1,15 @@ import json import jsonschema +from jsonschema import Draft6Validator, RefResolver +import os from lofar.sas.tmss.tmss.exceptions import * -from lofar.common.json_utils import validate_only +from lofar.common.json_utils import add_defaults_to_json_object_for_schema as json_utils_add_defaults_to_json + + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +TMSS_SCHEMA_PATH = BASE_DIR + "/tmssapp/schemas/" +TMSS_COMMON_BASE_SCHEMA = "base.json" +# TODO: validation is not the correct name any more rename to schema.py or schema_utils.py ? def validate_json_against_schema(json_string: str, schema: str): '''validate the given json_string against the given schema. @@ -28,8 +36,107 @@ def validate_json_against_schema(json_string: str, schema: str): # now do the actual validation try: - jsonschema.validate(json_object, schema_object) - # validate_only(json_object, schema_object) - # should this be changed?? + validate_json_object_with_schema(json_object, schema_object) except jsonschema.ValidationError as e: raise SchemaValidationException(str(e)) + + +def get_default_json_object_for_schema(schema: str) -> dict: + """ + TMSS wrapper for TMSS 'add_defaults_to_json_object_for_schema' + :param schema: + :return: json_object with default values of the schema + """ + return add_defaults_to_json_object_for_schema({}, schema) + + +def add_defaults_to_json_object_for_schema(json_object: dict, schema: str) -> dict: + """ + TMSS wrapper for 'json_utils.add_defaults_to_json_object_for_schema' to use RefResolver when needed + :param schema: + :return: json_object with default values of the schema added to the given json_object + """ + if check_ref_in_schema(schema): + resolver = create_local_ref_resolver() + copy_of_json_object = json_utils_add_defaults_to_json(json_object, schema, resolver) + else: + copy_of_json_object = json_utils_add_defaults_to_json(json_object, schema) + return copy_of_json_object + + +def validate_json_object_with_schema(json_object, schema): + """ + Validate the given json_object with schema + When required use RefResolver + :param json_object: + :param schema: + :return: + """ + if check_ref_in_schema(schema): + resolver = create_local_ref_resolver() + Draft6Validator(schema=schema, resolver=resolver).validate(json_object) + else: + Draft6Validator(schema=schema).validate(json_object) + + +def item_generator(json_input, lookup_key): + """ + A function to find a key in nested json structure + :param json_input: + :param lookup_key: + :return: values of the key found in json + """ + if isinstance(json_input, dict): + for k, v in json_input.items(): + if k == lookup_key: + yield v + else: + yield from item_generator(v, lookup_key) + elif isinstance(json_input, list): + for item in json_input: + yield from item_generator(item, lookup_key) + + +def check_ref_in_schema(schema): + """ + Check if there is a reference to the base schema (base.json) for the given schema + :param schema: + :return: True or False + """ + ref_to_base = False + for ref in list(item_generator(schema, "$ref")): + if TMSS_COMMON_BASE_SCHEMA in ref: + ref_to_base = True + return ref_to_base + + +def create_local_ref_resolver(abs_path_to_schema=TMSS_SCHEMA_PATH, base_schema_file_name=TMSS_COMMON_BASE_SCHEMA): + """ + Create jsonschema.RefResolver class for all schemas located in given path. + Apparently if you using jsonschema you have to set a reference resolver to referred schemas + for local files: + https://stackoverflow.com/questions/25145160/json-schema-ref-does-not-work-for-relative-path + https://stackoverflow.com/questions/53968770/how-to-set-up-local-file-references-in-python-jsonschema-document + https://stackoverflow.com/questions/42159346/jsonschema-refresolver-to-resolve-multiple-refs-in-python + :param abs_path_to_schema: Absolute path to location of all schemas + :param base_schema_file_name: The name of the common base schema where other schema's refer too + :return resolver_object: RefResolver object + """ + schema_store = {} + with open(os.path.join(abs_path_to_schema, base_schema_file_name), 'r') as fp: + def_schema = json.load(fp) + + file_name_list = os.listdir(abs_path_to_schema) + for file_name in file_name_list: + file_full_path = os.path.join(abs_path_to_schema, file_name) + if file_full_path.endswith(".json"): + with open(file_full_path, "r") as fp: + schema = json.load(fp) + if "$id" in schema: + schema_store[schema["$id"]] = schema + # wondering if schema_store is still required ... ?? + # https://stackoverflow.com/questions/42159346/jsonschema-refresolver-to-resolve-multiple-refs-in-python + base_uri = "file:///" + abs_path_to_schema + base_schema_file_name + resolver = RefResolver(base_uri=base_uri, referrer=def_schema, store=schema_store) + return resolver + diff --git a/SAS/TMSS/src/tmss/tmssapp/views.py b/SAS/TMSS/src/tmss/tmssapp/views.py index bfb670fd87b..695f70ba616 100644 --- a/SAS/TMSS/src/tmss/tmssapp/views.py +++ b/SAS/TMSS/src/tmss/tmssapp/views.py @@ -3,7 +3,7 @@ import os from django.http import HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, render from lofar.sas.tmss.tmss.tmssapp import models -from lofar.common.json_utils import get_default_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import get_default_json_object_for_schema from lofar.sas.tmss.tmss.tmssapp.adapters.parset import convert_to_parset diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py index e1ce15a72ab..3b1b00922cb 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py @@ -24,7 +24,7 @@ from lofar.sas.tmss.tmss.tmssapp import models from lofar.sas.tmss.tmss.tmssapp import serializers from datetime import datetime -from lofar.common.json_utils import get_default_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import get_default_json_object_for_schema from lofar.common.datetimeutils import formatDatetime from lofar.sas.tmss.tmss.tmssapp.adapters.parset import convert_to_parset from drf_yasg.renderers import _SpecRenderer diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py index 93ab6971734..312d47f6c72 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py @@ -21,7 +21,7 @@ from lofar.sas.tmss.tmss.tmssapp import models from lofar.sas.tmss.tmss.tmssapp import serializers from datetime import datetime -from lofar.common.json_utils import get_default_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import get_default_json_object_for_schema from lofar.common.datetimeutils import formatDatetime from lofar.sas.tmss.tmss.tmssapp.tasks import * from lofar.sas.tmss.tmss.tmssapp.subtasks import * diff --git a/SAS/TMSS/test/t_parset_adapter.py b/SAS/TMSS/test/t_parset_adapter.py index 5d1c9052402..984075abc5c 100755 --- a/SAS/TMSS/test/t_parset_adapter.py +++ b/SAS/TMSS/test/t_parset_adapter.py @@ -40,7 +40,7 @@ rest_data_creator = TMSSRESTTestDataCreator(BASE_URL, AUTH) from lofar.sas.tmss.tmss.tmssapp import models from lofar.sas.tmss.tmss.tmssapp.adapters.parset import convert_to_parset -from lofar.common.json_utils import get_default_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import get_default_json_object_for_schema class ParsetAdapterTest(unittest.TestCase): def test_01(self): diff --git a/SAS/TMSS/test/tmss_test_data_django_models.py b/SAS/TMSS/test/tmss_test_data_django_models.py index 2d8a16334f7..06a72d47782 100644 --- a/SAS/TMSS/test/tmss_test_data_django_models.py +++ b/SAS/TMSS/test/tmss_test_data_django_models.py @@ -28,7 +28,7 @@ which is automatically destroyed at the end of the unittest session. ####################################################### from lofar.sas.tmss.tmss.tmssapp import models -from lofar.common.json_utils import get_default_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import get_default_json_object_for_schema from datetime import datetime import uuid diff --git a/SAS/TMSS/test/tmss_test_data_rest.py b/SAS/TMSS/test/tmss_test_data_rest.py index ada401713f6..784e73f6edd 100644 --- a/SAS/TMSS/test/tmss_test_data_rest.py +++ b/SAS/TMSS/test/tmss_test_data_rest.py @@ -25,7 +25,7 @@ from datetime import datetime import uuid import requests import json -from lofar.common.json_utils import get_default_json_object_for_schema +from lofar.sas.tmss.tmss.tmssapp.validation import get_default_json_object_for_schema class TMSSRESTTestDataCreator(): def __init__(self, django_api_url: str, auth: requests.auth.HTTPBasicAuth): -- GitLab