From a971e23e09e447eeccd8026f483348788b78e38a Mon Sep 17 00:00:00 2001 From: JB Lovland Date: Mon, 29 Jan 2024 16:48:30 +0100 Subject: [PATCH] DEP: jsonschema dependency --- .github/workflows/mypy.yml | 2 + pyproject.toml | 1 - src/fmu/dataio/dataio.py | 2 +- src/fmu/dataio/datastructure/meta/meta.py | 22 +- ...gic_pydantic.py => test_pydantic_logic.py} | 184 ++++---- tests/test_schema/test_schema_logic.py | 414 ------------------ 6 files changed, 100 insertions(+), 525 deletions(-) rename tests/test_schema/{test_schema_logic_pydantic.py => test_pydantic_logic.py} (58%) delete mode 100644 tests/test_schema/test_schema_logic.py diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 1a4adfda3..28d9ac610 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -13,6 +13,8 @@ jobs: - name: Set up python uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} - name: Install dev-env. run: | diff --git a/pyproject.toml b/pyproject.toml index 6bfff6835..20aea5561 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,6 @@ Documentation = "https://fmu-dataio.readthedocs.io" dev = [ "coverage>=4.1", "hypothesis", - "jsonschema", "mypy", "pydocstyle", "pytest-cov", diff --git a/src/fmu/dataio/dataio.py b/src/fmu/dataio/dataio.py index 17e3385ce..b7ae6158d 100644 --- a/src/fmu/dataio/dataio.py +++ b/src/fmu/dataio/dataio.py @@ -95,7 +95,7 @@ def _check_global_config( Currently far from a full validation. For now, just check that some required keys are present in the config and warn/raise if not. - PS! Seems like a good job for jsonschema, but the produced error message are not + PS! Seems like a good job for pydantic, but the produced error message are not informative enough to provide meaningful information to user when something is wrong. """ diff --git a/src/fmu/dataio/datastructure/meta/meta.py b/src/fmu/dataio/datastructure/meta/meta.py index 6b263012b..c4f124a67 100644 --- a/src/fmu/dataio/datastructure/meta/meta.py +++ b/src/fmu/dataio/datastructure/meta/meta.py @@ -176,7 +176,7 @@ class RealizationJobs(BaseModel): data_root: Path = Field(alias="DATA_ROOT") ert_pid: str global_environment: Dict[str, str] - global_update_path: dict + global_update_path: Dict job_list: List[RealizationJobListing] = Field(alias="jobList") run_id: str umask: str @@ -363,18 +363,18 @@ class Root( ] ] ): - @model_validator(mode="before") - @classmethod - def _check_class_data_spec(cls, values: Dict) -> Dict: - class_ = values.get("class_") - data = values.get("data") - - if class_ in ["table", "surface"] and (data is None or "spec" not in data): + @model_validator(mode="after") + def _check_class_data_spec(self) -> Root: + if ( + self.root.class_ in (enums.FMUClassEnum.table, enums.FMUClassEnum.surface) + and hasattr(self.root, "data") + and self.root.data.root.spec is None + ): raise ValueError( "When 'class' is 'table' or 'surface', " "'data' must contain the 'spec' field." ) - return values + return self @classmethod def __get_pydantic_json_schema__( @@ -393,8 +393,8 @@ def __get_pydantic_json_schema__( return json_schema -def dump() -> dict: - return dict( +def dump() -> Dict: + return Dict( ChainMap( { "$contractual": [ diff --git a/tests/test_schema/test_schema_logic_pydantic.py b/tests/test_schema/test_pydantic_logic.py similarity index 58% rename from tests/test_schema/test_schema_logic_pydantic.py rename to tests/test_schema/test_pydantic_logic.py index b546d8a3c..c969c29d1 100644 --- a/tests/test_schema/test_schema_logic_pydantic.py +++ b/tests/test_schema/test_pydantic_logic.py @@ -2,24 +2,19 @@ import logging from copy import deepcopy -import jsonschema +import conftest import pytest -from conftest import metadata_examples from fmu.dataio._definitions import ALLOWED_CONTENTS -from fmu.dataio.datastructure.meta import Root, dump +from fmu.dataio.datastructure.meta import Root from fmu.dataio.datastructure.meta.enums import ContentEnum +from pydantic import ValidationError # pylint: disable=no-member logger = logging.getLogger(__name__) -@pytest.fixture(scope="session") -def pydantic_schema(): - return dump() - - -@pytest.mark.parametrize("file, example", metadata_examples().items()) +@pytest.mark.parametrize("file, example", conftest.metadata_examples().items()) def test_schema_example_filenames(file, example): """Assert that all examples are .yml, not .yaml""" assert file.endswith(".yml") @@ -30,19 +25,13 @@ def test_schema_example_filenames(file, example): # ====================================================================================== -@pytest.mark.parametrize("file, example", metadata_examples().items()) -def test_jsonschema_validate(pydantic_schema, file, example): - """Confirm that examples are valid against the schema""" - jsonschema.validate(instance=example, schema=pydantic_schema) - - -@pytest.mark.parametrize("file, example", metadata_examples().items()) -def test_pydantic_model_validate(pydantic_schema, file, example): +@pytest.mark.parametrize("file, example", conftest.metadata_examples().items()) +def test_validate(file, example): """Confirm that examples are valid against the schema""" Root.model_validate(example) -def test_pydantic_schema_file_block(pydantic_schema, metadata_examples): +def test_schema_file_block(metadata_examples): """Test variations on the file block.""" # get a specific example @@ -50,57 +39,57 @@ def test_pydantic_schema_file_block(pydantic_schema, metadata_examples): # Root.model_validate(example) # shall validate as-is - jsonschema.validate(instance=example, schema=pydantic_schema) + Root.model_validate(example) # shall validate without absolute_path _example = deepcopy(example) del _example["file"]["absolute_path"] - jsonschema.validate(instance=_example, schema=pydantic_schema) + Root.model_validate(_example) # md5 checksum shall be a string _example["file"]["checksum_md5"] = 123.4 - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) # shall not validate without checksum_md5 del _example["file"]["checksum_md5"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) # shall validate when checksum is put back in _example["file"]["checksum_md5"] = "somechecksum" - jsonschema.validate(instance=_example, schema=pydantic_schema) + Root.model_validate(_example) # shall not validate without relative_path del _example["file"]["relative_path"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) -def test_pydantic_schema_logic_case(pydantic_schema, metadata_examples): +def test_case(metadata_examples): """Asserting validation failure when illegal contents in case example""" example = metadata_examples["case.yml"] # assert validation with no changes - jsonschema.validate(instance=example, schema=pydantic_schema) + Root.model_validate(example) # assert validation error when "fmu" is missing _example = deepcopy(example) del _example["fmu"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) # assert validation error when "fmu.model" is missing _example = deepcopy(example) del _example["fmu"]["model"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) -def test_pydantic_schema_logic_fmu_block_aggr_real(pydantic_schema, metadata_examples): +def test_fmu_block_aggr_real(metadata_examples): """Test that fmu.realization and fmu.aggregation are not allowed at the same time""" metadata = deepcopy(metadata_examples["surface_depth.yml"]) @@ -109,17 +98,17 @@ def test_pydantic_schema_logic_fmu_block_aggr_real(pydantic_schema, metadata_exa assert "aggregation" not in metadata["fmu"] # assert validation as-is - jsonschema.validate(instance=metadata, schema=pydantic_schema) + Root.model_validate(metadata) # add aggregation, shall fail. Get this from an actual example that validates. _metadata_aggregation = metadata_examples["aggregated_surface_depth.yml"] metadata["fmu"]["aggregation"] = _metadata_aggregation["fmu"]["aggregation"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=metadata, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(metadata) -def test_pydantic_schema_logic_data_top_base(pydantic_schema, metadata_examples): +def test_data_top_base(metadata_examples): """Test require data.top and data.base. * Require both data.top and data.base, or none. @@ -132,28 +121,28 @@ def test_pydantic_schema_logic_data_top_base(pydantic_schema, metadata_examples) assert "base" in metadata["data"] # assert validation as-is - jsonschema.validate(instance=metadata, schema=pydantic_schema) + Root.model_validate(metadata) # remove "top" - shall fail _metadata = deepcopy(metadata) del _metadata["data"]["top"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_metadata) # remove "base" - shall fail _metadata = deepcopy(metadata) del _metadata["data"]["base"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_metadata) # remove both - shall pass del _metadata["data"]["top"] assert "top" not in _metadata["data"] # test assumption assert "base" not in _metadata["data"] # test assumption - jsonschema.validate(instance=_metadata, schema=pydantic_schema) + Root.model_validate(_metadata) -def test_pydantic_schema_logic_field_outline(pydantic_schema, metadata_examples): +def test_field_outline(metadata_examples): """Test content-specific rule. When content == field_outline, require the field_outline field @@ -166,16 +155,16 @@ def test_pydantic_schema_logic_field_outline(pydantic_schema, metadata_examples) assert "field_outline" in metadata["data"] # assert validation as-is - jsonschema.validate(instance=metadata, schema=pydantic_schema) + Root.model_validate(metadata) # assert failure when content is field_outline and fluid_contact is missing _metadata = deepcopy(metadata) del _metadata["data"]["field_outline"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_metadata) -def test_pydantic_schema_logic_field_region(pydantic_schema, metadata_examples): +def test_field_region(metadata_examples): """Test content-specific rule: field_region When content == field_outline, require the data.field_region field. @@ -187,27 +176,27 @@ def test_pydantic_schema_logic_field_region(pydantic_schema, metadata_examples): assert metadata["data"]["content"] == "field_region" assert "field_region" in metadata["data"] assert "id" in metadata["data"]["field_region"] - jsonschema.validate(instance=metadata, schema=pydantic_schema) + Root.model_validate(metadata) # assert that data.field_region is required _metadata = deepcopy(metadata) del _metadata["data"]["field_region"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_metadata) # validation of data.field_region _metadata = deepcopy(metadata) del _metadata["data"]["field_region"]["id"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_metadata) _metadata = deepcopy(metadata) _metadata["data"]["field_region"]["id"] = "NotANumber" - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_metadata) -def test_pydantic_schema_logic_fluid_contact(pydantic_schema, metadata_examples): +def test_fluid_contact(metadata_examples): """Test content-specific rule. When content == fluid_contact, require the fluid_contact field @@ -223,30 +212,30 @@ def test_pydantic_schema_logic_fluid_contact(pydantic_schema, metadata_examples) # assert failure when content is fluid_contact and fluid_contact block missing _metadata = deepcopy(metadata) del _metadata["data"]["fluid_contact"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_metadata) -def test_pydantic_schema_masterdata_smda(pydantic_schema, metadata_examples): +def test_schema_masterdata_smda(metadata_examples): """Test schema logic for masterdata.smda.""" example = metadata_examples["case.yml"] # assert validation with no changes - jsonschema.validate(instance=example, schema=pydantic_schema) + Root.model_validate(example) # assert validation error when masterdata block is missing _example = deepcopy(example) del _example["masterdata"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) # assert validation error when masterdata.smda is missing # print(example["masterdata"]) _example = deepcopy(example) del _example["masterdata"]["smda"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) # assert validation error when missing attribute for block in [ @@ -258,8 +247,8 @@ def test_pydantic_schema_masterdata_smda(pydantic_schema, metadata_examples): ]: _example = deepcopy(example) del _example["masterdata"]["smda"][block] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) # assert validation error if not correct type for block, type_ in [ @@ -273,11 +262,11 @@ def test_pydantic_schema_masterdata_smda(pydantic_schema, metadata_examples): _example["masterdata"]["smda"][block] = "somestring" - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) -def test_pydantic_schema_data_time(pydantic_schema, metadata_examples): +def test_schema_data_time(metadata_examples): """Test schema logic for data.time.""" # fetch one example that contains the data.time element @@ -285,23 +274,23 @@ def test_pydantic_schema_data_time(pydantic_schema, metadata_examples): assert "time" in example["data"] # assert validation with no changes - jsonschema.validate(instance=example, schema=pydantic_schema) + Root.model_validate(example) # valid when data.time is missing _example = deepcopy(example) del _example["data"]["time"] - jsonschema.validate(instance=_example, schema=pydantic_schema) + Root.model_validate(_example) # valid when only t0 _example = deepcopy(example) del _example["data"]["time"]["t1"] assert "t0" in _example["data"]["time"] # test assumption - jsonschema.validate(instance=_example, schema=pydantic_schema) + Root.model_validate(_example) # valid without labels _example = deepcopy(example) del _example["data"]["time"]["t0"]["label"] - jsonschema.validate(instance=_example, schema=pydantic_schema) + Root.model_validate(_example) # NOT valid when other types for testvalue in [ @@ -312,56 +301,56 @@ def test_pydantic_schema_data_time(pydantic_schema, metadata_examples): ]: _example = deepcopy(example) _example["data"]["time"] = testvalue - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(_example) -def test_schema_logic_classification(pydantic_schema, metadata_examples): +def test_classification(metadata_examples): """Test the classification of individual files.""" # fetch example example = deepcopy(metadata_examples["surface_depth.yml"]) # assert validation with no changes - jsonschema.validate(instance=example, schema=pydantic_schema) + Root.model_validate(example) # assert "internal" and "restricted" validates example["access"]["classification"] = "internal" - jsonschema.validate(instance=example, schema=pydantic_schema) + Root.model_validate(example) example["access"]["classification"] = "restricted" - jsonschema.validate(instance=example, schema=pydantic_schema) + Root.model_validate(example) # assert erroneous value does not validate example["access"]["classification"] = "open" - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=example, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(example) -def test_schema_logic_data_spec(pydantic_schema, metadata_examples): +def test_data_spec(metadata_examples): """Test schema logic for data.spec""" # fetch surface example example_surface = deepcopy(metadata_examples["surface_depth.yml"]) # assert validation with no changes - jsonschema.validate(instance=example_surface, schema=pydantic_schema) + Root.model_validate(example_surface) # assert data.spec required when class == surface del example_surface["data"]["spec"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=example_surface, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(example_surface) # fetch table example example_table = deepcopy(metadata_examples["table_inplace.yml"]) # assert validation with no changes - jsonschema.validate(instance=example_table, schema=pydantic_schema) + Root.model_validate(example_table) # assert data.spec required when class == table del example_table["data"]["spec"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=example_table, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(example_table) # fetch dictionary example example_dict = deepcopy(metadata_examples["dictionary_parameters.yml"]) @@ -371,10 +360,10 @@ def test_schema_logic_data_spec(pydantic_schema, metadata_examples): example_dict["data"]["spec"] # assert data.spec not required when class === dictionary - jsonschema.validate(instance=example_dict, schema=pydantic_schema) + Root.model_validate(example_dict) -def test_schema_logic_content_whitelist(pydantic_schema, metadata_examples): +def test_content_whitelist(metadata_examples): """Test that validation fails when value of data.content is not in the whitelist.""" @@ -382,19 +371,18 @@ def test_schema_logic_content_whitelist(pydantic_schema, metadata_examples): example_surface = deepcopy(metadata_examples["surface_depth.yml"]) # assert validation with no changes - jsonschema.validate(instance=example_surface, schema=pydantic_schema) + Root.model_validate(example_surface) # shall fail when content is not in whitelist example_surface["data"]["content"] = "not_valid_content" - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=example_surface, schema=pydantic_schema) + with pytest.raises(ValidationError): + Root.model_validate(example_surface) -def test_schema_content_synch_with_code(): +@pytest.mark.parametrize("allowed_content", ALLOWED_CONTENTS.keys()) +def test_schema_content_synch_with_code(allowed_content): """Currently, the whitelist for content is maintained both in the schema and in the code. This test asserts that list used in the code is in synch with schema. Note! This is one-way, and will not fail if additional elements are added to the schema only.""" - - for allowed_content in ALLOWED_CONTENTS: - assert allowed_content in {v.name for v in ContentEnum} + assert allowed_content in {v.name for v in ContentEnum} diff --git a/tests/test_schema/test_schema_logic.py b/tests/test_schema/test_schema_logic.py deleted file mode 100644 index 8625a666b..000000000 --- a/tests/test_schema/test_schema_logic.py +++ /dev/null @@ -1,414 +0,0 @@ -"""Test the schema""" -import logging -from copy import deepcopy - -import jsonschema -import pytest -from fmu.dataio._definitions import ALLOWED_CONTENTS - -# pylint: disable=no-member - -logger = logging.getLogger(__name__) - - -def test_schema_basic_json_syntax(schema_080): - """Confirm that schemas are valid JSON.""" - - assert "$schema" in schema_080 - - -def test_schema_example_filenames(metadata_examples): - """Assert that all examples are .yml, not .yaml""" - - # check that examples are there - assert len(metadata_examples) > 0 - - for filename in metadata_examples: - assert filename.endswith(".yml"), filename - - -# ====================================================================================== -# 0.8.0 -# ====================================================================================== - - -def test_schema_080_validate_examples_as_is(schema_080, metadata_examples): - """Confirm that examples are valid against the schema""" - - for i, (name, metadata) in enumerate(metadata_examples.items()): - try: - jsonschema.validate(instance=metadata, schema=schema_080) - except jsonschema.exceptions.ValidationError: - logger.error("Failed validating existing example: %s", name) - if i == 0: - logger.error( - "This was the first example attempted." - "Error is most likely int he schema." - ) - else: - logger.error( - "This was not the first example attemted." - "Error is most likely in the example." - ) - raise - - -def test_schema_080_file_block(schema_080, metadata_examples): - """Test variations on the file block.""" - - # get a specific example - example = metadata_examples["surface_depth.yml"] - - # shall validate as-is - jsonschema.validate(instance=example, schema=schema_080) - - # shall validate without absolute_path - _example = deepcopy(example) - del _example["file"]["absolute_path"] - jsonschema.validate(instance=_example, schema=schema_080) - - # md5 checksum shall be a string - _example["file"]["checksum_md5"] = 123.4 - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - # shall not validate without checksum_md5 - del _example["file"]["checksum_md5"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - # shall validate when checksum is put back in - _example["file"]["checksum_md5"] = "somechecksum" - jsonschema.validate(instance=_example, schema=schema_080) - - # shall not validate without relative_path - del _example["file"]["relative_path"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - -def test_schema_080_logic_case(schema_080, metadata_examples): - """Asserting validation failure when illegal contents in case example""" - - example = metadata_examples["case.yml"] - - # assert validation with no changes - jsonschema.validate(instance=example, schema=schema_080) - - # assert validation error when "fmu" is missing - _example = deepcopy(example) - del _example["fmu"] - - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - # assert validation error when "fmu.model" is missing - _example = deepcopy(example) - del _example["fmu"]["model"] - - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - -def test_schema_080_logic_fmu_block_aggr_real(schema_080, metadata_examples): - """Test that fmu.realization and fmu.aggregation are not allowed at the same time""" - - metadata = deepcopy(metadata_examples["surface_depth.yml"]) - # check that assumptions for the test is true - assert "realization" in metadata["fmu"] - assert "aggregation" not in metadata["fmu"] - - # assert validation as-is - jsonschema.validate(instance=metadata, schema=schema_080) - - # add aggregation, shall fail. Get this from an actual example that validates. - _metadata_aggregation = metadata_examples["aggregated_surface_depth.yml"] - metadata["fmu"]["aggregation"] = _metadata_aggregation["fmu"]["aggregation"] - - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=metadata, schema=schema_080) - - -def test_schema_080_logic_data_top_base(schema_080, metadata_examples): - """Test require data.top and data.base. - - * Require both data.top and data.base, or none. - """ - - metadata = metadata_examples["surface_seismic_amplitude.yml"] - - # check that assumptions for the test is true - assert "top" in metadata["data"] - assert "base" in metadata["data"] - - # assert validation as-is - jsonschema.validate(instance=metadata, schema=schema_080) - - # remove "top" - shall fail - _metadata = deepcopy(metadata) - del _metadata["data"]["top"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=schema_080) - - # remove "base" - shall fail - _metadata = deepcopy(metadata) - del _metadata["data"]["base"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=schema_080) - - # remove both - shall pass - del _metadata["data"]["top"] - assert "top" not in _metadata["data"] # test assumption - assert "base" not in _metadata["data"] # test assumption - jsonschema.validate(instance=_metadata, schema=schema_080) - - -def test_schema_080_logic_field_outline(schema_080, metadata_examples): - """Test content-specific rule. - - When content == field_outline, require the field_outline field - """ - - metadata = metadata_examples["polygons_field_outline.yml"] - - # check that assumptions for the test is true - assert metadata["data"]["content"] == "field_outline" - assert "field_outline" in metadata["data"] - - # assert validation as-is - jsonschema.validate(instance=metadata, schema=schema_080) - - # assert failure when content is field_outline and fluid_contact is missing - _metadata = deepcopy(metadata) - del _metadata["data"]["field_outline"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=schema_080) - - -def test_schema_080_logic_field_region(schema_080, metadata_examples): - """Test content-specific rule: field_region - - When content == field_outline, require the data.field_region field. - """ - - metadata = metadata_examples["polygons_field_region.yml"] - - # check assumptions - assert metadata["data"]["content"] == "field_region" - assert "field_region" in metadata["data"] - assert "id" in metadata["data"]["field_region"] - jsonschema.validate(instance=metadata, schema=schema_080) - - # assert that data.field_region is required - _metadata = deepcopy(metadata) - del _metadata["data"]["field_region"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=schema_080) - - # validation of data.field_region - _metadata = deepcopy(metadata) - del _metadata["data"]["field_region"]["id"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=schema_080) - - _metadata = deepcopy(metadata) - _metadata["data"]["field_region"]["id"] = "NotANumber" - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=schema_080) - - -def test_schema_080_logic_fluid_contact(schema_080, metadata_examples): - """Test content-specific rule. - - When content == fluid_contact, require the fluid_contact field - """ - - # parse the schema and polygons - metadata = metadata_examples["surface_fluid_contact.yml"] - - # check that assumptions for the test is true - assert metadata["data"]["content"] == "fluid_contact" - assert "fluid_contact" in metadata["data"] - - # assert failure when content is fluid_contact and fluid_contact block missing - _metadata = deepcopy(metadata) - del _metadata["data"]["fluid_contact"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_metadata, schema=schema_080) - - -def test_schema_080_masterdata_smda(schema_080, metadata_examples): - """Test schema logic for masterdata.smda.""" - - example = metadata_examples["case.yml"] - - # assert validation with no changes - jsonschema.validate(instance=example, schema=schema_080) - - # assert validation error when masterdata block is missing - _example = deepcopy(example) - del _example["masterdata"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - # assert validation error when masterdata.smda is missing - # print(example["masterdata"]) - _example = deepcopy(example) - del _example["masterdata"]["smda"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - # assert validation error when missing attribute - for block in [ - "country", - "discovery", - "field", - "coordinate_system", - "stratigraphic_column", - ]: - _example = deepcopy(example) - del _example["masterdata"]["smda"][block] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - # assert validation error if not correct type - for block, type_ in [ - ("country", list), - ("discovery", list), - ("coordinate_system", dict), - ("stratigraphic_column", dict), - ]: - _example = deepcopy(example) - assert isinstance(_example["masterdata"]["smda"][block], type_) - - _example["masterdata"]["smda"][block] = "somestring" - - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - -def test_schema_080_data_time(schema_080, metadata_examples): - """Test schema logic for data.time.""" - - # fetch one example that contains the data.time element - example = metadata_examples["surface_seismic_amplitude.yml"] - assert "time" in example["data"] - - # assert validation with no changes - jsonschema.validate(instance=example, schema=schema_080) - - # valid when data.time is missing - _example = deepcopy(example) - del _example["data"]["time"] - jsonschema.validate(instance=_example, schema=schema_080) - - # valid when only t0 - _example = deepcopy(example) - del _example["data"]["time"]["t1"] - assert "t0" in _example["data"]["time"] # test assumption - jsonschema.validate(instance=_example, schema=schema_080) - - # valid without labels - _example = deepcopy(example) - del _example["data"]["time"]["t0"]["label"] - jsonschema.validate(instance=_example, schema=schema_080) - - # NOT valid when other types - for testvalue in [ - [{"t0": "2020-10-28T14:28:02", "label": "mylabel"}], - "2020-10-28T14:28:02", - 123, - 123.4, - ]: - _example = deepcopy(example) - _example["data"]["time"] = testvalue - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=_example, schema=schema_080) - - -def test_schema_logic_classification(schema_080, metadata_examples): - """Test the classification of individual files.""" - - # fetch example - example = deepcopy(metadata_examples["surface_depth.yml"]) - - # assert validation with no changes - jsonschema.validate(instance=example, schema=schema_080) - - # assert "internal" and "restricted" validates - example["access"]["classification"] = "internal" - jsonschema.validate(instance=example, schema=schema_080) - - example["access"]["classification"] = "restricted" - jsonschema.validate(instance=example, schema=schema_080) - - # assert erroneous value does not validate - example["access"]["classification"] = "open" - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=example, schema=schema_080) - - -def test_schema_logic_data_spec(schema_080, metadata_examples): - """Test schema logic for data.spec""" - - # fetch surface example - example_surface = deepcopy(metadata_examples["surface_depth.yml"]) - - # assert validation with no changes - jsonschema.validate(instance=example_surface, schema=schema_080) - - # assert data.spec required when class == surface - del example_surface["data"]["spec"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=example_surface, schema=schema_080) - - # fetch table example - example_table = deepcopy(metadata_examples["table_inplace.yml"]) - - # assert validation with no changes - jsonschema.validate(instance=example_table, schema=schema_080) - - # assert data.spec required when class == table - del example_table["data"]["spec"] - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=example_table, schema=schema_080) - - # fetch dictionary example - example_dict = deepcopy(metadata_examples["dictionary_parameters.yml"]) - - # assert data.spec is not present - with pytest.raises(KeyError): - example_dict["data"]["spec"] - - # assert data.spec not required when class === dictionary - jsonschema.validate(instance=example_dict, schema=schema_080) - - -def test_schema_logic_content_whitelist(schema_080, metadata_examples): - """Test that validation fails when value of data.content is not in - the whitelist.""" - - # fetch surface example - example_surface = deepcopy(metadata_examples["surface_depth.yml"]) - - # assert validation with no changes - jsonschema.validate(instance=example_surface, schema=schema_080) - - # shall fail when content is not in whitelist - example_surface["data"]["content"] = "not_valid_content" - with pytest.raises(jsonschema.exceptions.ValidationError): - jsonschema.validate(instance=example_surface, schema=schema_080) - - -def test_schema_content_synch_with_code(schema_080): - """Currently, the whitelist for content is maintained both in the schema - and in the code. This test asserts that list used in the code is in synch - with schema. Note! This is one-way, and will not fail if additional - elements are added to the schema only.""" - - schema_allowed = schema_080["definitions"]["data"]["properties"]["content"]["enum"] - for allowed_content in ALLOWED_CONTENTS: - if allowed_content not in schema_allowed: - raise ValueError( - f"content '{allowed_content}' allowed in code, but not schema." - )