From 7d76fb8d373b6891f0e16c8aff50093fd36352ff Mon Sep 17 00:00:00 2001 From: Christian Sandberg Date: Thu, 19 Sep 2024 13:19:41 +0200 Subject: [PATCH] Rename validate() to validate_input() to not conflict with Pydantic --- README.md | 8 +-- onedm/sdf/common.py | 4 +- onedm/sdf/data.py | 82 ++++++++---------------------- onedm/sdf/definitions.py | 16 +++--- tests/sdf/test_required.py | 29 +++++++++++ tests/sdf/test_value_validation.py | 28 +++++----- 6 files changed, 76 insertions(+), 91 deletions(-) create mode 100644 tests/sdf/test_required.py diff --git a/README.md b/README.md index b0cf474..4699515 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,13 @@ Loading an existing SDF document: >>> doc.properties["IntegerProperty"] IntegerProperty(observable=True, readable=True, writable=True, label='Example integer', description=None, ref=None, required=[], type=, sdf_type=None, nullable=True, const=2, unit=None, minimum=-2, maximum=2, exclusive_minimum=None, exclusive_maximum=None, multiple_of=2, format=None, choices=None, default=None) ->>> doc.data["Integer"].validate(3) +>>> doc.data["Integer"].validate_input(3) Traceback (most recent call last): File "", line 1, in - File "onedm\sdf\data.py", line 129, in validate - return super().validate(input) + File "onedm\sdf\data.py", line 129, in validate_input + return super().validate_input(input) ^^^^^^^^^^^^^^^^^^^^^^^ - File "onedm\sdf\data.py", line 64, in validate + File "onedm\sdf\data.py", line 64, in validate_input return SchemaValidator(self.get_pydantic_schema()).validate_python(input) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pydantic_core._pydantic_core.ValidationError: 1 validation error for constrained-int diff --git a/onedm/sdf/common.py b/onedm/sdf/common.py index ec3e89f..e3a6c0a 100644 --- a/onedm/sdf/common.py +++ b/onedm/sdf/common.py @@ -5,9 +5,7 @@ class CommonQualities(BaseModel): - model_config = ConfigDict( - extra="allow", alias_generator=to_camel - ) + model_config = ConfigDict(extra="allow", alias_generator=to_camel) label: str | None = None description: str | None = None diff --git a/onedm/sdf/data.py b/onedm/sdf/data.py index c4ed4da..c3ac55f 100644 --- a/onedm/sdf/data.py +++ b/onedm/sdf/data.py @@ -10,7 +10,7 @@ import datetime from typing import Annotated, Any, Literal, Union -from pydantic import Field, NonNegativeInt, model_validator +from pydantic import Field, NonNegativeInt from pydantic_core import SchemaValidator, core_schema from .common import CommonQualities @@ -50,13 +50,13 @@ def get_pydantic_schema(self) -> core_schema.CoreSchema: schema = core_schema.nullable_schema(schema) return schema - def validate(self, input: Any) -> Any: + def validate_input(self, input: Any) -> Any: """Validate and coerce a value.""" return SchemaValidator(self.get_pydantic_schema()).validate_python(input) class NumberData(DataQualities): - type: Literal["number"] + type: Literal["number"] = "number" unit: str | None = None minimum: float | None = None maximum: float | None = None @@ -67,13 +67,6 @@ class NumberData(DataQualities): const: float | None = None default: float | None = None - @model_validator(mode="before") - @classmethod - def set_default_type(cls, data: Any): - if isinstance(data, dict): - data.setdefault("type", "number") - return data - def _get_base_schema(self) -> core_schema.FloatSchema | core_schema.DatetimeSchema: if self.sdf_type == "unix-time": return core_schema.datetime_schema( @@ -106,12 +99,12 @@ def _get_base_schema(self) -> core_schema.FloatSchema | core_schema.DatetimeSche multiple_of=self.multiple_of, ) - def validate(self, input: Any) -> float: - return super().validate(input) + def validate_input(self, input: Any) -> float: + return super().validate_input(input) class IntegerData(DataQualities): - type: Literal["integer"] + type: Literal["integer"] = "integer" unit: str | None = None minimum: int | None = None maximum: int | None = None @@ -122,13 +115,6 @@ class IntegerData(DataQualities): const: int | None = None default: int | None = None - @model_validator(mode="before") - @classmethod - def set_default_type(cls, data: Any): - if isinstance(data, dict): - data.setdefault("type", "integer") - return data - def _get_base_schema(self) -> core_schema.IntSchema: return core_schema.int_schema( ge=self.minimum, @@ -138,31 +124,24 @@ def _get_base_schema(self) -> core_schema.IntSchema: multiple_of=self.multiple_of, ) - def validate(self, input: Any) -> int: - return super().validate(input) + def validate_input(self, input: Any) -> int: + return super().validate_input(input) class BooleanData(DataQualities): - type: Literal["boolean"] + type: Literal["boolean"] = "boolean" const: bool | None = None default: bool | None = None - @model_validator(mode="before") - @classmethod - def set_default_type(cls, data: Any): - if isinstance(data, dict): - data.setdefault("type", "boolean") - return data - def _get_base_schema(self) -> core_schema.BoolSchema: return core_schema.bool_schema() - def validate(self, input: Any) -> bool: - return super().validate(input) + def validate_input(self, input: Any) -> bool: + return super().validate_input(input) class StringData(DataQualities): - type: Literal["string"] + type: Literal["string"] = "string" enum: list[str] | None = None min_length: NonNegativeInt = 0 max_length: NonNegativeInt | None = None @@ -173,13 +152,6 @@ class StringData(DataQualities): const: str | None = None default: str | None = None - @model_validator(mode="before") - @classmethod - def set_default_type(cls, data: Any): - if isinstance(data, dict): - data.setdefault("type", "string") - return data - def _get_base_schema( self, ) -> core_schema.StringSchema | core_schema.BytesSchema | core_schema.LiteralSchema: @@ -205,12 +177,12 @@ def _get_base_schema( pattern=self.pattern, ) - def validate(self, input: Any) -> str | bytes: - return super().validate(input) + def validate_input(self, input: Any) -> str | bytes: + return super().validate_input(input) class ArrayData(DataQualities): - type: Literal["array"] + type: Literal["array"] = "array" min_items: NonNegativeInt = 0 max_items: NonNegativeInt | None = None unique_items: bool = False @@ -218,13 +190,6 @@ class ArrayData(DataQualities): const: list | None = None default: list | None = None - @model_validator(mode="before") - @classmethod - def set_default_type(cls, data: Any): - if isinstance(data, dict): - data.setdefault("type", "array") - return data - def _get_base_schema(self) -> core_schema.ListSchema | core_schema.SetSchema: if self.unique_items: return core_schema.set_schema( @@ -238,24 +203,17 @@ def _get_base_schema(self) -> core_schema.ListSchema | core_schema.SetSchema: max_length=self.max_items, ) - def validate(self, input: Any) -> list | set: - return super().validate(input) + def validate_input(self, input: Any) -> list | set: + return super().validate_input(input) class ObjectData(DataQualities): - type: Literal["object"] + type: Literal["object"] = "object" required: list[str] = Field(default_factory=list) properties: dict[str, Data] | None = None const: dict[str, Any] | None = None default: dict[str, Any] | None = None - @model_validator(mode="before") - @classmethod - def set_default_type(cls, data: Any): - if isinstance(data, dict): - data.setdefault("type", "object") - return data - def _get_base_schema(self) -> core_schema.TypedDictSchema: required = self.required or [] fields = { @@ -266,8 +224,8 @@ def _get_base_schema(self) -> core_schema.TypedDictSchema: } return core_schema.typed_dict_schema(fields) - def validate(self, input: Any) -> dict: - return super().validate(input) + def validate_input(self, input: Any) -> dict: + return super().validate_input(input) class AnyData(DataQualities): diff --git a/onedm/sdf/definitions.py b/onedm/sdf/definitions.py index 659acac..1269cce 100644 --- a/onedm/sdf/definitions.py +++ b/onedm/sdf/definitions.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Annotated, Literal, Union +from typing import Annotated, Literal, Union, Tuple from pydantic import Field, NonNegativeInt @@ -20,49 +20,49 @@ class NumberProperty(NumberData): observable: bool = True readable: bool = True writable: bool = True - required: list[Literal[True]] | None = Field(default=None, alias="sdfRequired") + required: Tuple[Literal[True]] | None = Field(default=None, alias="sdfRequired") class IntegerProperty(IntegerData): observable: bool = True readable: bool = True writable: bool = True - required: list[Literal[True]] | None = Field(default=None, alias="sdfRequired") + required: Tuple[Literal[True]] | None = Field(default=None, alias="sdfRequired") class BooleanProperty(BooleanData): observable: bool = True readable: bool = True writable: bool = True - required: list[Literal[True]] | None = Field(default=None, alias="sdfRequired") + required: Tuple[Literal[True]] | None = Field(default=None, alias="sdfRequired") class StringProperty(StringData): observable: bool = True readable: bool = True writable: bool = True - required: list[Literal[True]] | None = Field(default=None, alias="sdfRequired") + required: Tuple[Literal[True]] | None = Field(default=None, alias="sdfRequired") class ArrayProperty(ArrayData): observable: bool = True readable: bool = True writable: bool = True - required: list[Literal[True]] | None = Field(default=None, alias="sdfRequired") + required: Tuple[Literal[True]] | None = Field(default=None, alias="sdfRequired") class ObjectProperty(ObjectData): observable: bool = True readable: bool = True writable: bool = True - required: list[Literal[True]] | None = Field(default=None, alias="sdfRequired") + required: Tuple[Literal[True]] | None = Field(default=None, alias="sdfRequired") class AnyProperty(AnyData): observable: bool = True readable: bool = True writable: bool = True - required: list[Literal[True]] | None = Field(default=None, alias="sdfRequired") + required: Tuple[Literal[True]] | None = Field(default=None, alias="sdfRequired") Property = Union[ diff --git a/tests/sdf/test_required.py b/tests/sdf/test_required.py new file mode 100644 index 0000000..bcf8558 --- /dev/null +++ b/tests/sdf/test_required.py @@ -0,0 +1,29 @@ +from onedm import sdf + + +def test_required_as_list(): + test = sdf.Object.model_validate( + { + "sdfProperty": { + "prop1": {}, + "prop2": {}, + }, + "sdfRequired": ["prop1"] + } + ) + assert "prop1" in test.required + + +def test_required_as_prop(): + test = sdf.Object.model_validate( + { + "sdfProperty": { + "prop1": { + "type": "integer", + "sdfRequired": [True], + }, + "prop2": {}, + }, + } + ) + assert test.properties["prop1"].required diff --git a/tests/sdf/test_value_validation.py b/tests/sdf/test_value_validation.py index 32ac983..4258e5b 100644 --- a/tests/sdf/test_value_validation.py +++ b/tests/sdf/test_value_validation.py @@ -4,49 +4,49 @@ def test_integer_validation(): integer = sdf.IntegerData(maximum=2) - assert integer.validate(2) == 2 + assert integer.validate_input(2) == 2 with pytest.raises(ValueError): - integer.validate(1.5) + integer.validate_input(1.5) # Out of range with pytest.raises(ValueError): - integer.validate(3) + integer.validate_input(3) def test_number_validation(test_model: sdf.SDF): - assert test_model.data["Number"].validate(0.5) == 0.5 - assert test_model.data["Number"].validate(1) == 1.0 + assert test_model.data["Number"].validate_input(0.5) == 0.5 + assert test_model.data["Number"].validate_input(1) == 1.0 # Out of range with pytest.raises(ValueError): - test_model.data["Number"].validate(100) + test_model.data["Number"].validate_input(100) # Invalid type (array) with pytest.raises(ValueError): - test_model.data["Number"].validate([1.0]) + test_model.data["Number"].validate_input([1.0]) # Invalid type (string) with pytest.raises(ValueError): - test_model.data["Number"].validate("string") + test_model.data["Number"].validate_input("string") # Invalid multiple of with pytest.raises(ValueError): - test_model.data["Number"].validate(0.1) + test_model.data["Number"].validate_input(0.1) def test_string_validation(test_model: sdf.SDF): - assert test_model.data["String"].validate("0123456789") == "0123456789" + assert test_model.data["String"].validate_input("0123456789") == "0123456789" # Invalid length with pytest.raises(ValueError): - test_model.data["Number"].validate("too short") + test_model.data["Number"].validate_input("too short") # Invalid type (array) with pytest.raises(ValueError): - test_model.data["Number"].validate(["0123456789"]) + test_model.data["Number"].validate_input(["0123456789"]) def test_nullable_validation(): nullable_integer = sdf.IntegerData(nullable=True) - assert nullable_integer.validate(None) == None + assert nullable_integer.validate_input(None) == None def test_non_nullable_validation(): integer = sdf.IntegerData(nullable=False) with pytest.raises(ValueError): - integer.validate(None) + integer.validate_input(None)