Skip to content

Commit

Permalink
Rename validate() to validate_input() to not conflict with Pydantic
Browse files Browse the repository at this point in the history
  • Loading branch information
christiansandberg committed Sep 19, 2024
1 parent db29595 commit 7d76fb8
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 91 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<DataType.INTEGER: 'integer'>, 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 "<stdin>", line 1, in <module>
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
Expand Down
4 changes: 1 addition & 3 deletions onedm/sdf/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 20 additions & 62 deletions onedm/sdf/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -205,26 +177,19 @@ 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
items: Data | None = None
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(
Expand All @@ -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 = {
Expand All @@ -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):
Expand Down
16 changes: 8 additions & 8 deletions onedm/sdf/definitions.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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[
Expand Down
29 changes: 29 additions & 0 deletions tests/sdf/test_required.py
Original file line number Diff line number Diff line change
@@ -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
28 changes: 14 additions & 14 deletions tests/sdf/test_value_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 7d76fb8

Please sign in to comment.