Repository: bo4e/BO4E-python (language: python)

Issue:
Implement COM Preis
https://www.bo4e.de/dokumentation/komponenten/com-preis

requires 
- #125 
- #118  



Discussion / hints:

Fix patch:
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)


Test patch:
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 <class 'decimal.Decimal'>" 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)