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

Issue:
Implement COM Tarifpreisposition
https://www.bo4e.de/dokumentation/komponenten/com-tarifpreisposition
requires
- #134 
- #125 
- #140 


Discussion / hints:

Fix patch:
diff --git a/src/bo4e/com/tarifpreisposition.py b/src/bo4e/com/tarifpreisposition.py
new file mode 100644
index 000000000..05f08466b
--- /dev/null
+++ b/src/bo4e/com/tarifpreisposition.py
@@ -0,0 +1,69 @@
+"""
+Contains Tarifpreisposition class
+and corresponding marshmallow schema for de-/serialization
+"""
+
+from typing import Optional, List
+
+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.com.preisstaffel import Preisstaffel, PreisstaffelSchema
+from bo4e.enum.preistyp import Preistyp
+from bo4e.enum.waehrungseinheit import Waehrungseinheit
+from bo4e.enum.mengeneinheit import Mengeneinheit
+from bo4e.validators import check_list_length_at_least_one
+
+# pylint: disable=too-few-public-methods
+@attr.s(auto_attribs=True, kw_only=True)
+class Tarifpreisposition(COM):
+    """
+    Mit dieser Komponente können Tarifpreise verschiedener Typen abgebildet werden.
+    """
+
+    # required attributes
+    # Angabe des Preistypes (z.B. Grundpreis)
+    preistyp: Preistyp = attr.ib(validator=attr.validators.instance_of(Preistyp))
+    # Einheit des Preises (z.B. EURO)
+    einheit: Waehrungseinheit = attr.ib(validator=attr.validators.instance_of(Waehrungseinheit))
+    # Größe, auf die sich die Einheit bezieht, beispielsweise kWh, Jahr
+    bezugseinheit: Mengeneinheit = attr.ib(validator=attr.validators.instance_of(Mengeneinheit))
+    # Hier sind die Staffeln mit ihren Preisenangaben definiert
+    preisstaffeln: List[Preisstaffel] = attr.ib(
+        validator=[
+            attr.validators.deep_iterable(
+                member_validator=attr.validators.instance_of(Preisstaffel),
+                iterable_validator=attr.validators.instance_of(list),
+            ),
+            check_list_length_at_least_one,
+        ]
+    )
+
+    # optional attributes
+    # Gibt an, nach welcher Menge die vorgenannte Einschränkung erfolgt (z.B. Jahresstromverbrauch in kWh)
+    mengeneinheitstaffel: Optional[Mengeneinheit] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Mengeneinheit))
+    )
+
+
+class TarifpreispositionSchema(COMSchema):
+    """
+    Schema for de-/serialization of Tarifpreisposition.
+    """
+
+    # required attributes
+    preistyp = EnumField(Preistyp)
+    einheit = EnumField(Waehrungseinheit)
+    bezugseinheit = EnumField(Mengeneinheit)
+    preisstaffeln = fields.List(fields.Nested(PreisstaffelSchema))
+
+    # optional attributes
+    mengeneinheitstaffel = EnumField(Mengeneinheit, load_default=None)
+
+    # pylint: disable=no-self-use, unused-argument
+    @post_load
+    def deserialize(self, data, **kwargs) -> Tarifpreisposition:
+        """Deserialize JSON to Tarifpreisposition object"""
+        return Tarifpreisposition(**data)


Test patch:
diff --git a/tests/test_tarifpreisposition.py b/tests/test_tarifpreisposition.py
new file mode 100644
index 000000000..d4aa2ea52
--- /dev/null
+++ b/tests/test_tarifpreisposition.py
@@ -0,0 +1,99 @@
+from decimal import Decimal
+
+import pytest  # type:ignore[import]
+
+from bo4e.com.tarifpreisposition import Tarifpreisposition, TarifpreispositionSchema
+from bo4e.com.preisstaffel import Preisstaffel
+from bo4e.enum.preistyp import Preistyp
+from bo4e.enum.waehrungseinheit import Waehrungseinheit
+from bo4e.enum.mengeneinheit import Mengeneinheit
+from tests.serialization_helper import assert_serialization_roundtrip  # type:ignore[import]
+
+
+class TestTarifpreisposition:
+    @pytest.mark.parametrize(
+        "tarifpreisposition, expected_json_dict",
+        [
+            pytest.param(
+                Tarifpreisposition(
+                    preistyp=Preistyp.ENTGELT_ABLESUNG,
+                    einheit=Waehrungseinheit.EUR,
+                    bezugseinheit=Mengeneinheit.KWH,
+                    preisstaffeln=[
+                        Preisstaffel(
+                            einheitspreis=Decimal(40.0),
+                            staffelgrenze_von=Decimal(12.5),
+                            staffelgrenze_bis=Decimal(25.0),
+                        ),
+                    ],
+                ),
+                {
+                    "preistyp": "ENTGELT_ABLESUNG",
+                    "einheit": "EUR",
+                    "bezugseinheit": "KWH",
+                    "preisstaffeln": [
+                        {
+                            "einheitspreis": "40",
+                            "sigmoidparameter": None,
+                            "staffelgrenzeBis": "25",
+                            "staffelgrenzeVon": "12.5",
+                        }
+                    ],
+                    "mengeneinheitstaffel": None,
+                },
+                id="only required attributes",
+            ),
+            pytest.param(
+                Tarifpreisposition(
+                    preistyp=Preistyp.ENTGELT_ABLESUNG,
+                    einheit=Waehrungseinheit.EUR,
+                    bezugseinheit=Mengeneinheit.KWH,
+                    preisstaffeln=[
+                        Preisstaffel(
+                            einheitspreis=Decimal(40.0),
+                            staffelgrenze_von=Decimal(12.5),
+                            staffelgrenze_bis=Decimal(25.0),
+                        ),
+                    ],
+                    mengeneinheitstaffel=Mengeneinheit.STUECK,
+                ),
+                {
+                    "preistyp": "ENTGELT_ABLESUNG",
+                    "einheit": "EUR",
+                    "bezugseinheit": "KWH",
+                    "preisstaffeln": [
+                        {
+                            "einheitspreis": "40",
+                            "sigmoidparameter": None,
+                            "staffelgrenzeBis": "25",
+                            "staffelgrenzeVon": "12.5",
+                        }
+                    ],
+                    "mengeneinheitstaffel": "STUECK",
+                },
+                id="optional and required attributes",
+            ),
+        ],
+    )
+    def test_serialization_roundtrip(self, tarifpreisposition: Tarifpreisposition, expected_json_dict: dict):
+        """
+        Test de-/serialisation of Tarifpreisposition.
+        """
+        assert_serialization_roundtrip(tarifpreisposition, TarifpreispositionSchema(), expected_json_dict)
+
+    def test_missing_required_attribute(self):
+        with pytest.raises(TypeError) as excinfo:
+            _ = Tarifpreisposition()
+
+        assert "missing 4 required" in str(excinfo.value)
+
+    def test_tarifpreisposition_betraege_required(self):
+        with pytest.raises(ValueError) as excinfo:
+            _ = Tarifpreisposition(
+                preistyp=Preistyp.ENTGELT_ABLESUNG,
+                einheit=Waehrungseinheit.EUR,
+                bezugseinheit=Mengeneinheit.KWH,
+                preisstaffeln=[],
+            )
+
+        assert "List preisstaffeln must not be empty." in str(excinfo.value)