diff --git a/.gitignore b/.gitignore index c68be1b..8e237ab 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ __pycache__ build src/rune.runtime.egg-info rune.runtime-*.whl -src/rune/runtime/version.py \ No newline at end of file +src/rune/runtime/version.py +/.coverage diff --git a/pyproject.toml b/pyproject.toml index e018721..438a733 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ ] optional-dependencies.dev = [ "pytest", + "pytest-cov", "pytest-mock" ] description = "rune-runtime: the Rune DSL runtime for Python" diff --git a/src/rune/runtime/metadata.py b/src/rune/runtime/metadata.py index 4151548..7816e54 100644 --- a/src/rune/runtime/metadata.py +++ b/src/rune/runtime/metadata.py @@ -1,14 +1,15 @@ '''Classes representing annotated basic Rune types''' -from enum import Enum +import uuid +import datetime import importlib +from enum import Enum from functools import partial, lru_cache -import uuid from decimal import Decimal from typing import Any, Never, get_args -import datetime from typing_extensions import Self, Tuple from pydantic import (PlainSerializer, PlainValidator, WrapValidator, WrapSerializer) +from pydantic_core import PydanticCustomError # from rune.runtime.object_registry import get_object META_CONTAINER = '__rune_metadata' @@ -365,6 +366,11 @@ def deserialize(cls, obj, allowed_meta: set[str]): if isinstance(obj, Reference): return obj + if not isinstance(obj, dict): + raise PydanticCustomError('Input Validation Error', + 'Expected either {my_type} or dict but ' + 'got {type}.', + {'type': type(obj), 'my_type': cls}) metadata = {k: obj[k] for k in obj.keys() if k.startswith('@')} # References deserialization treatment diff --git a/test/cdm/test_validation.py b/test/cdm/test_validation.py new file mode 100644 index 0000000..6f928dd --- /dev/null +++ b/test/cdm/test_validation.py @@ -0,0 +1,23 @@ +'''Full attribute validation - pydantic and constraints''' +import pytest +from pydantic import ValidationError +try: + # pylint: disable=unused-import + # type: ignore + from cdm.base.math.NonNegativeQuantity import NonNegativeQuantity + from cdm.base.math.UnitType import UnitType + NO_SER_TEST_MOD = False +except ImportError: + NO_SER_TEST_MOD = True + + +@pytest.mark.skipif(NO_SER_TEST_MOD, reason='CDM package not found') +def test_bad_attrib_validation(): + '''Invalid attribute assigned''' + unit = UnitType(currency='EUR') + mq = NonNegativeQuantity(value=10, unit=unit) + mq.frequency = 'Blah' + with pytest.raises(ValidationError): + mq.validate_model() + +# EOF diff --git a/test/serializer-round-trip/test_enumtype.py b/test/serializer-round-trip/test_enumtype.py index ab23c75..db13daa 100644 --- a/test/serializer-round-trip/test_enumtype.py +++ b/test/serializer-round-trip/test_enumtype.py @@ -15,10 +15,19 @@ @pytest.mark.skipif(NO_SER_TEST_MOD, reason='Generated test package not found') def test_enum_types_single(): '''no doc''' + # import serialization.test.passing.enumtypes.Root + # import serialization.test.passing.enumtypes.EnumSingle + # import serialization.test.passing.enumtypes.EnumType + + # root = serialization.test.passing.enumtypes.Root.Root( + # enumSingle=serialization.test.passing.enumtypes.EnumSingle.EnumSingle( + # enumType=serialization.test.passing.enumtypes.EnumType.EnumType.A + # )) + # resp_json = root.rune_serialize() json_str = ''' { "@model": "serialization", - "@type": "serialization.test.enumtypes.Root", + "@type": "serialization.test.passing.enumtypes.Root", "@version": "0.0.0", "enumSingle": { "enumType": "A" @@ -36,7 +45,7 @@ def test_enum_types_list(): json_str = ''' { "@model": "serialization", - "@type": "serialization.test.enumtypes.Root", + "@type": "serialization.test.passing.enumtypes.Root", "@version": "0.0.0", "enumList": { "enumType": ["A", "B", "C", "B"] diff --git a/test/serializer-round-trip/test_extension.py b/test/serializer-round-trip/test_extension.py index aa86846..f70cf5c 100644 --- a/test/serializer-round-trip/test_extension.py +++ b/test/serializer-round-trip/test_extension.py @@ -18,7 +18,7 @@ def test_base_type(): '''no doc''' json_str = '''{ "@model": "serialization", - "@type": "serialization.test.extension.Root", + "@type": "serialization.test.passing.extension.Root", "@version": "0.0.0", "typeA": { "fieldA": "foo" @@ -34,12 +34,12 @@ def test_extended_type_concrete(): '''no doc''' json_str = '''{ "@model": "serialization", - "@type": "serialization.test.extension.Root", + "@type": "serialization.test.passing.extension.Root", "@version": "0.0.0", "typeB": { "fieldA": "foo", "fieldB": "foo", - "@type": "serialization.test.extension.B" + "@type": "serialization.test.passing.extension.B" } }''' model = BaseDataClass.rune_deserialize(json_str) @@ -55,15 +55,15 @@ def test_extended_type_polymorphic(): '''no doc''' json_str = '''{ "@model": "serialization", - "@type": "serialization.test.extension.Root", + "@type": "serialization.test.passing.extension.Root", "@version": "0.0.0", "typeA": { "fieldA": "bar", "fieldB": "foo", - "@type": "serialization.test.extension.B" + "@type": "serialization.test.passing.extension.B" } }''' - # import serialization.test.extension.Root + # import serialization.test.passing.extension.Root model = BaseDataClass.rune_deserialize(json_str) resp_json = model.rune_serialize() assert json.loads(resp_json) == json.loads(json_str) @@ -72,14 +72,14 @@ def test_extended_type_polymorphic(): @pytest.mark.skipif(NO_SER_TEST_MOD, reason='Generated test package not found') def test_at_type(): '''no doc''' - from serialization.test.extension.B import B + from serialization.test.passing.extension.B import B json_str = ''' { - "@type": "serialization.test.extension.Root", + "@type": "serialization.test.passing.extension.Root", "typeA" : { "fieldA" : "bar", "fieldB" : "foo", - "@type" : "serialization.test.extension.B" + "@type" : "serialization.test.passing.extension.B" } } ''' @@ -90,7 +90,7 @@ def test_at_type(): @pytest.mark.skipif(NO_SER_TEST_MOD, reason='Generated test package not found') def test_temp(): '''no doc''' - from serialization.test.metakey.Root import Root + from serialization.test.passing.metakey.Root import Root json_str = ''' { "nodeRef" : { @@ -112,7 +112,7 @@ def test_temp(): @pytest.mark.skipif(NO_SER_TEST_MOD, reason='Generated test package not found') def test_enums(): '''no doc''' - from serialization.test.enumtypes.Root import Root + from serialization.test.passing.enumtypes.Root import Root json_str = '{"enumSingle":{"enumType":"A"}}' json_dict = json.loads(json_str) model1 = Root.model_validate(json_dict) diff --git a/test/serializer-round-trip/test_metakey.py b/test/serializer-round-trip/test_metakey.py index 56782c9..744f321 100644 --- a/test/serializer-round-trip/test_metakey.py +++ b/test/serializer-round-trip/test_metakey.py @@ -172,7 +172,7 @@ def test_generated_attribute_ref(): '''attribute-ref.json''' json_str = '''{ "@model": "serialization", - "@type": "serialization.test.metakey.Root", + "@type": "serialization.test.passing.metakey.Root", "@version": "0.0.0", "attributeRef": { "dateField": { @@ -198,7 +198,7 @@ def test_generated_node_ref(): '''attribute-ref.json''' json_str = '''{ "@model": "serialization", - "@type": "serialization.test.metakey.Root", + "@type": "serialization.test.passing.metakey.Root", "@version": "0.0.0", "nodeRef": { "typeA": { @@ -224,7 +224,7 @@ def test_generated_dangling_node_ref(): '''attribute-ref.json''' json_str = '''{ "@model": "serialization", - "@type": "serialization.test.metakey.Root", + "@type": "serialization.test.passing.metakey.Root", "@version": "0.0.0", "nodeRef": { "aReference": { @@ -243,7 +243,7 @@ def test_generated_dangling_attribute_ref(): '''attribute-ref.json''' json_str = '''{ "@model": "serialization", - "@type": "serialization.test.metakey.Root", + "@type": "serialization.test.passing.metakey.Root", "@version": "0.0.0", "attributeRef": { "dateReference": { diff --git a/test/serializer-round-trip/test_metalocation.py b/test/serializer-round-trip/test_metalocation.py index 0bf4d56..b0e0bc5 100644 --- a/test/serializer-round-trip/test_metalocation.py +++ b/test/serializer-round-trip/test_metalocation.py @@ -18,7 +18,7 @@ def test_address(): '''no doc''' json_str = ''' { "@model": "serialization", - "@type": "serialization.test.metalocation.Root", + "@type": "serialization.test.passing.metalocation.Root", "@version": "0.0.0", "typeA": { "b": { diff --git a/test/serializer-round-trip/test_metascheme.py b/test/serializer-round-trip/test_metascheme.py index cc529d0..bf4f936 100644 --- a/test/serializer-round-trip/test_metascheme.py +++ b/test/serializer-round-trip/test_metascheme.py @@ -18,7 +18,7 @@ def test_enum_single(): json_str = ''' { "@model": "serialization", - "@type": "serialization.test.metascheme.Root", + "@type": "serialization.test.passing.metascheme.Root", "@version": "0.0.0", "enumType": { "@data": "A", @@ -37,7 +37,7 @@ def test_enum_list(): json_str = ''' { "@model": "serialization", - "@type": "serialization.test.metascheme.Root", + "@type": "serialization.test.passing.metascheme.Root", "@version": "0.0.0", "enumTypeList": [{ "@data": "A",