from typing import Any
from typing import List
from typing import Literal
import numpy
from pydantic import Field
from ..models._base.custom_types.metadata.quantity import REGISTRY
from ..models._base.custom_types.metadata.quantity import ensure_dimension
from ..models._base.custom_types.metadata.quantity import ensure_units
from ..models._base.custom_types.quantity import PydanticQuantity
from ..models._base.model import IcatNexusBaseModel
from ..models._base.nxmodels import NXobject
from ..models._base.quantity import MILLIMETERS
from . import compare
from .assert_raises import raises_validationerror
from .validation_error import dimensionality_error
from .validation_error import dimensionless_error
from .validation_error import magnitude_int_error
from .validation_error import sequence_int_error
[docs]
def test_fixed_units_and_type():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", int]
# Test unit conversion
model = TestModel(length=[1, "m"])
assert str(model.length) == "1000 mm"
_assert_model_dump(model, REGISTRY.Quantity(1000, "mm"), 1000, 1000, 1000)
model.length = 2, "m"
assert str(model.length) == "2000 mm"
# Test failing unit conversion
with raises_validationerror(
dimensionality_error([10, "kg"], "kilogram", "millimeter", "[mass]", "[length]")
):
TestModel(length=[10, "kg"])
# Test value type coercion
model = TestModel(length=[1.5, "m"])
assert str(model.length) == "1500 mm"
_assert_model_dump(model, REGISTRY.Quantity(1500, "mm"), 1500, 1500, 1500)
model = TestModel(length=["1.5", "m"])
assert str(model.length) == "1500 mm"
_assert_model_dump(model, REGISTRY.Quantity(1500, "mm"), 1500, 1500, 1500)
model.length = 2.5, "m"
assert str(model.length) == "2500 mm"
# Test default units
model = TestModel(length=[10, None])
assert str(model.length) == "10 mm"
_assert_model_dump(model, REGISTRY.Quantity(10, "mm"), 10, 10, 10)
model = TestModel(length=[10])
assert str(model.length) == "10 mm"
_assert_model_dump(model, REGISTRY.Quantity(10, "mm"), 10, 10, 10)
model.length = 20
assert str(model.length) == "20 mm"
model.length = 30
assert str(model.length) == "30 mm"
# Test failing type coercion
with raises_validationerror(magnitude_int_error(1.5, 1.5)):
TestModel(length=1.5)
with raises_validationerror(magnitude_int_error(2.5, 2.5)):
model.length = 2.5
with raises_validationerror(magnitude_int_error([10, "µm"], 0.01)):
model = TestModel(length=[10, "µm"])
with raises_validationerror(magnitude_int_error((10, "µm"), 0.01)):
model.length = 10, "µm"
[docs]
def test_fixed_dimensions_and_type():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"[length]", float]
mass: PydanticQuantity[r"[mass]", int]
model = TestModel(length=[5, "cm"], mass=[3000, "g"])
assert str(model.length) == "5.0 cm"
assert str(model.mass) == "3000 g"
model.length = 6, "m"
model.mass = 10.0, "mg"
assert str(model.length) == "6.0 m"
assert str(model.mass) == "10 mg"
[docs]
def test_fixed_units_and_any_type():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: MILLIMETERS
model = TestModel(length=[1, "m"])
assert str(model.length) == "1000 mm"
_assert_model_dump(model, REGISTRY.Quantity(1000, "mm"), 1000, 1000, 1000)
model = TestModel(length=[1.0, "m"])
assert str(model.length) == "1000.0 mm"
_assert_model_dump(model, REGISTRY.Quantity(1000, "mm"), 1000, 1000, 1000)
model = TestModel(length=[100, "um"])
assert str(model.length) == "0.1 mm"
_assert_model_dump(model, REGISTRY.Quantity(0.1, "mm"), 0.1, 0.1, 0.1)
[docs]
def test_dimensionless_and_any_type():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[None]
model = TestModel(length=1)
assert str(model.length) == "1"
_assert_model_dump(model, REGISTRY.Quantity(1, "dimensionless"), 1, 1, 1)
model = TestModel(length=1.0)
assert str(model.length) == "1.0"
_assert_model_dump(model, REGISTRY.Quantity(1, "dimensionless"), 1.0, 1.0, 1.0)
with raises_validationerror(dimensionless_error([10, "kg"])):
TestModel(length=[10, "kg"])
[docs]
def test_dimensionless_and_fixed_type():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[None, int]
model = TestModel(length=1)
assert str(model.length) == "1"
_assert_model_dump(model, REGISTRY.Quantity(1, "dimensionless"), 1, 1, 1)
model = TestModel(length=1.0)
assert str(model.length) == "1"
_assert_model_dump(model, REGISTRY.Quantity(1, "dimensionless"), 1, 1, 1)
with raises_validationerror(magnitude_int_error(1.5, 1.5)):
TestModel(length=1.5)
with raises_validationerror(dimensionless_error([10, "kg"])):
TestModel(length=[10, "kg"])
[docs]
def test_ensure_units():
q = ensure_units((10, "m"), "cm", float)
assert isinstance(q.magnitude, float)
assert str(q) == "1000.0 cm"
for unit in ("photons/second", "photons/s", "photon/second", "photon/s"):
value = ensure_units(100, unit, None)
assert value.to("kHz").magnitude == 0.1
[docs]
def test_check_dimension():
q = ensure_dimension((10, "keV"), "[energy]", float)
assert isinstance(q.magnitude, float)
assert str(q) == "10.0 keV"
q = ensure_dimension((10, "J"), "[energy]", int)
assert isinstance(q.magnitude, int)
assert str(q) == "10 J"
[docs]
def test_array_units():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", List[int]]
model = TestModel(length=[[1, 2, 3], "mm"])
assert str(model.length) == "[1 2 3] mm"
_assert_model_dump(
model,
REGISTRY.Quantity([1, 2, 3], "mm"),
[1, 2, 3],
numpy.array([1, 2, 3]),
"1 2 3",
),
[docs]
def test_array_default_unit():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", List[float]]
model = TestModel(length=[[1, 2, 3], None])
assert str(model.length) == "[1.0 2.0 3.0] mm"
_assert_model_dump(
model,
REGISTRY.Quantity([1, 2, 3], "mm"),
[1, 2, 3],
numpy.array([1, 2, 3]),
"1.0 2.0 3.0",
)
model = TestModel(length=[[1, 2, 3]])
assert str(model.length) == "[1.0 2.0 3.0] mm"
_assert_model_dump(
model,
REGISTRY.Quantity([1, 2, 3], "mm"),
[1, 2, 3],
numpy.array([1, 2, 3]),
"1.0 2.0 3.0",
)
[docs]
def test_array_unit_conversion():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", List[float]]
model = TestModel(length=[[1, 2, 3], "m"])
assert str(model.length) == "[1000.0 2000.0 3000.0] mm"
_assert_model_dump(
model,
REGISTRY.Quantity([1000, 2000, 3000], "mm"),
[1000, 2000, 3000],
numpy.array([1000, 2000, 3000]),
"1000.0 2000.0 3000.0",
)
model.length = [0.5, 1.5, 2.5], "m"
assert str(model.length) == "[500.0 1500.0 2500.0] mm"
_assert_model_dump(
model,
REGISTRY.Quantity([500.0, 1500.0, 2500.0], "mm"),
[500, 1500, 2500],
numpy.array([500, 1500, 2500]),
"500.0 1500.0 2500.0",
)
[docs]
def test_array_invalid_unit_conversion():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", List[float]]
with raises_validationerror(
dimensionality_error(
[[1, 2, 3], "kg"], "kilogram", "millimeter", "[mass]", "[length]"
)
):
TestModel(length=[[1, 2, 3], "kg"])
[docs]
def test_array_type_coercion():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", List[int]]
model = TestModel(length=[[1, 2, 3], "m"])
assert str(model.length) == "[1000 2000 3000] mm"
_assert_model_dump(
model,
REGISTRY.Quantity([1000, 2000, 3000], "mm"),
[1000, 2000, 3000],
numpy.array([1000, 2000, 3000]),
"1000 2000 3000",
)
with raises_validationerror(sequence_int_error([[1.2345, 2.3456], "m"], 1234.5)):
TestModel(length=[[1.2345, 2.3456], "m"])
[docs]
def test_array_type_coercion_with_strings():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", List[int]]
model = TestModel(length=[["1", "2", "3"], "mm"])
assert str(model.length) == "[1 2 3] mm"
_assert_model_dump(
model,
REGISTRY.Quantity([1, 2, 3], "mm"),
[1, 2, 3],
numpy.array([1, 2, 3]),
"1 2 3",
)
model = TestModel(length=[["1", "2", "3"], "m"])
assert str(model.length) == "[1000 2000 3000] mm"
_assert_model_dump(
model,
REGISTRY.Quantity([1000, 2000, 3000], "mm"),
[1000, 2000, 3000],
numpy.array([1000, 2000, 3000]),
"1000 2000 3000",
)
[docs]
def test_array_single_element_conversion():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", List[float]]
model = TestModel(length=[[1.5], "m"])
assert str(model.length) == "[1500.0] mm"
_assert_model_dump(
model, REGISTRY.Quantity([1500], "mm"), [1500], numpy.array([1500]), "1500.0"
)
[docs]
def test_array_empty_and_none_elements():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"mm", List[float]]
model = TestModel(length=[[], None])
assert str(model.length) == "[] mm"
_assert_model_dump(model, REGISTRY.Quantity([], "mm"), [], numpy.array([]), "")
model = TestModel(length=[[1, None, 3], None])
assert str(model.length) == "[1.0 3.0] mm"
_assert_model_dump(
model, REGISTRY.Quantity([1, 3], "mm"), [1, 3], numpy.array([1, 3]), "1.0 3.0"
)
[docs]
def test_array_dimension():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"[length]", List[float]]
model = TestModel(length=[[0.1, 0.2, 0.3], "m"])
assert str(model.length) == "[0.1 0.2 0.3] m"
_assert_model_dump(
model,
REGISTRY.Quantity([0.1, 0.2, 0.3], "m"),
[0.1, 0.2, 0.3],
numpy.array([0.1, 0.2, 0.3]),
"0.1 0.2 0.3",
)
[docs]
def test_array_dimension_conversion():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"[length]", List[float]]
model = TestModel(length=[[10, 20, 30], "cm"])
assert str(model.length) == "[10.0 20.0 30.0] cm"
_assert_model_dump(
model,
REGISTRY.Quantity([10, 20, 30], "cm"),
[10, 20, 30],
numpy.array([10, 20, 30]),
"10.0 20.0 30.0",
)
[docs]
def test_array_dimension_empty():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"[length]", List[float]]
model = TestModel(length=[[], "m"])
assert str(model.length) == "[] m"
_assert_model_dump(model, REGISTRY.Quantity([], "m"), [], numpy.array([]), "")
[docs]
def test_array_dimension_single_element():
class TestModel(NXobject):
NX_class: Literal["NXparameters"] = Field("NXparameters", alias="@NX_class")
length: PydanticQuantity[r"[length]", List[float]]
model = TestModel(length=[[0.5], "m"])
assert str(model.length) == "[0.5] m"
_assert_model_dump(
model, REGISTRY.Quantity([0.5], "m"), [0.5], numpy.array([0.5]), "0.5"
)
def _assert_model_dump(
model: IcatNexusBaseModel,
python_value: Any,
json_value: Any,
nexus_value: Any,
icat_value: Any,
):
compare.assert_equal_serialize_models(
model.model_dump(mode="python"),
{"NX_class": "NXparameters", "length": python_value},
)
compare.assert_equal_serialize_models(
model.model_dump(mode="json"),
{"NX_class": "NXparameters", "length": json_value},
)
compare.assert_equal_serialize_models(
model.model_dump(mode="nexus"),
{"NX_class": "NXparameters", "length": nexus_value},
)
compare.assert_equal_serialize_models(
model.model_dump(mode="icat"),
{"NX_class": "NXparameters", "length": icat_value},
)