diff --git a/src/bo4e/com/preis.py b/src/bo4e/com/preis.py new file mode 100644 index 000000000..452e0d3bb --- /dev/null +++ b/src/bo4e/com/preis.py @@ -0,0 +1,56 @@ +""" +Contains Preis class +and corresponding marshmallow schema for de-/serialization +""" + +from decimal import Decimal +from typing import Optional + +import attr +from marshmallow import fields, post_load +from marshmallow_enum import EnumField # type:ignore[import] + +from bo4e.com.com import COM, COMSchema +from bo4e.enum.mengeneinheit import Mengeneinheit +from bo4e.enum.preisstatus import Preisstatus +from bo4e.enum.waehrungseinheit import Waehrungseinheit + + +# pylint: disable=too-few-public-methods +@attr.s(auto_attribs=True, kw_only=True) +class Preis(COM): + """ + Abbildung eines Preises mit Wert, Einheit, Bezugswert und Status. + """ + + # required attributes + #: Gibt die nomiale Höhe des Preises an. + wert: Decimal = attr.ib(validator=attr.validators.instance_of(Decimal)) + #: Währungseinheit für den Preis, z.B. Euro oder Ct. + einheit: Waehrungseinheit = attr.ib(validator=attr.validators.in_(Waehrungseinheit)) + #: Angabe, für welche Bezugsgröße der Preis gilt. Z.B. kWh. + bezugswert: Mengeneinheit = attr.ib(validator=attr.validators.in_(Mengeneinheit)) + + # optional attributes + #: Gibt den Status des veröffentlichten Preises an + status: Optional[Preisstatus] = attr.ib(default=None) + + +class PreisSchema(COMSchema): + """ + Schema for de-/serialization of Preis. + """ + + # required attributes + wert = fields.Decimal(as_string=True) + einheit = EnumField(Waehrungseinheit) + bezugswert = EnumField(Mengeneinheit) + + # optional attributes + status = EnumField(Preisstatus, load_default=None) + + # pylint: disable=no-self-use, unused-argument + @post_load + def deserialize(self, data, **kwargs) -> Preis: + """Deserialize JSON to Preis object""" + return Preis(**data) diff --git a/tests/test_preis.py b/tests/test_preis.py new file mode 100644 index 000000000..2cb90848a --- /dev/null +++ b/tests/test_preis.py @@ -0,0 +1,60 @@ +from decimal import Decimal + +import pytest # type:ignore[import] + +from bo4e.com.preis import Preis, PreisSchema +from bo4e.enum.mengeneinheit import Mengeneinheit +from bo4e.enum.preisstatus import Preisstatus +from bo4e.enum.waehrungseinheit import Waehrungseinheit + + +class TestPreis: + def test_preis_only_required(self): + """ + Test de-/serialisation of Preis (only has required attributes). + """ + preis = Preis(wert=Decimal(2.53), einheit=Waehrungseinheit.EUR, bezugswert=Mengeneinheit.KWH) + + schema = PreisSchema() + json_string = schema.dumps(preis, ensure_ascii=False) + + assert "KWH" in json_string + assert "EUR" in json_string + assert "null" in json_string + + preis_deserialized = schema.loads(json_string) + + assert isinstance(preis_deserialized.wert, Decimal) + assert isinstance(preis_deserialized.einheit, Waehrungseinheit) + assert isinstance(preis_deserialized.bezugswert, Mengeneinheit) + assert preis_deserialized.status is None + assert preis == preis_deserialized + + def test_wrong_datatype(self): + with pytest.raises(TypeError) as excinfo: + _ = Preis(wert=3.50, einheit=Waehrungseinheit.EUR, bezugswert=Mengeneinheit.KWH) + + assert "'wert' must be " in str(excinfo.value) + + def test_missing_required_attribute(self): + with pytest.raises(TypeError) as excinfo: + _ = Preis(wert=Decimal(3.50), einheit=Waehrungseinheit.EUR, status=Preisstatus.ENDGUELTIG) + + assert "missing 1 required" in str(excinfo.value) + + def test_optional_attribute(self): + preis = Preis( + wert=Decimal(3.50), + einheit=Waehrungseinheit.EUR, + bezugswert=Mengeneinheit.KWH, + status=Preisstatus.ENDGUELTIG, + ) + + schema = PreisSchema() + json_string = schema.dumps(preis, ensure_ascii=False) + + assert "ENDGUELTIG" in json_string + + preis_deserialized = schema.loads(json_string) + + assert isinstance(preis_deserialized.status, Preisstatus)