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

Issue:
Implement COM Geraeteeigenschaften
https://www.bo4e.de/dokumentation/komponenten/com-geraeteeigenschaften
- #96 


Discussion / hints:

Fix patch:
diff --git a/src/bo4e/com/geraeteeigenschaften.py b/src/bo4e/com/geraeteeigenschaften.py
new file mode 100644
index 000000000..3a1d078b0
--- /dev/null
+++ b/src/bo4e/com/geraeteeigenschaften.py
@@ -0,0 +1,49 @@
+"""
+Contains Geraeteeigenschaften and corresponding marshmallow schema for de-/serialization
+"""
+from typing import Optional
+
+import attr
+from marshmallow import post_load
+from marshmallow_enum import EnumField  # type:ignore[import]
+
+from bo4e.com.com import COM, COMSchema
+
+# pylint: disable=too-few-public-methods
+from bo4e.enum.geraetemerkmal import Geraetemerkmal
+from bo4e.enum.geraetetyp import Geraetetyp
+
+
+@attr.s(auto_attribs=True, kw_only=True)
+class Geraeteeigenschaften(COM):
+    """
+    Mit dieser Komponente werden die Eigenschaften eines Gerätes in Bezug auf den Typ und weitere Merkmale modelliert
+    """
+
+    # required attributes
+    #: Der Typ eines Gerätes, beispielsweise Drehstromzähler
+    geraetetyp: Geraetetyp = attr.ib(validator=attr.validators.instance_of(Geraetetyp))
+
+    # optional attributes
+    #: Weitere Merkmale des Geräts, zum Beispiel Mehrtarif, Eintarif etc..
+    geraetemerkmal: Optional[Geraetemerkmal] = attr.ib(
+        default=None, validator=attr.validators.optional(attr.validators.instance_of(Geraetemerkmal))
+    )
+
+
+class GeraeteeigenschaftenSchema(COMSchema):
+    """
+    Schema for de-/serialization of Geraeteeigenschaften
+    """
+
+    # required attributes
+    geraetetyp = EnumField(Geraetetyp)
+
+    # optional attributes
+    geraetemerkmal = EnumField(Geraetemerkmal, allow_none=True)
+
+    # pylint: disable=no-self-use, unused-argument
+    @post_load
+    def deserialize(self, data, **kwargs) -> Geraeteeigenschaften:
+        """Deserialize JSON to Geraeteeigenschaften object"""
+        return Geraeteeigenschaften(**data)


Test patch:
diff --git a/tests/test_geraeteeigenschaften.py b/tests/test_geraeteeigenschaften.py
new file mode 100644
index 000000000..6e466ec64
--- /dev/null
+++ b/tests/test_geraeteeigenschaften.py
@@ -0,0 +1,42 @@
+import pytest  # type:ignore[import]
+
+from bo4e.com.geraeteeigenschaften import Geraeteeigenschaften, GeraeteeigenschaftenSchema
+from bo4e.enum.geraetemerkmal import Geraetemerkmal
+from bo4e.enum.geraetetyp import Geraetetyp
+from tests.serialization_helper import assert_serialization_roundtrip  # type:ignore[import]
+
+
+class TestGeraeteeigenschaften:
+    @pytest.mark.parametrize(
+        "geraeteeigenschaften, expected_json_dict",
+        [
+            pytest.param(
+                Geraeteeigenschaften(geraetemerkmal=Geraetemerkmal.GAS_G1000, geraetetyp=Geraetetyp.MULTIPLEXANLAGE),
+                {"geraetemerkmal": "GAS_G1000", "geraetetyp": "MULTIPLEXANLAGE"},
+            ),
+        ],
+    )
+    def test_serialization_roundtrip(self, geraeteeigenschaften: Geraeteeigenschaften, expected_json_dict: dict):
+        """
+        Test de-/serialisation of Geraeteeigenschaften
+        """
+        assert_serialization_roundtrip(geraeteeigenschaften, GeraeteeigenschaftenSchema(), expected_json_dict)
+
+    def test_missing_required_attribute(self):
+        with pytest.raises(TypeError) as excinfo:
+            _ = Geraeteeigenschaften()
+
+        assert "missing 1 required" in str(excinfo.value)
+
+    @pytest.mark.parametrize(
+        "not_a_geraetetyp",
+        [
+            pytest.param(17),  # not a geraetetyp
+            pytest.param("foo"),  # not a geraetetyp
+        ],
+    )
+    def test_failing_validation(self, not_a_geraetetyp):
+        with pytest.raises(TypeError) as excinfo:
+            _ = Geraeteeigenschaften(geraetemerkmal=Geraetemerkmal.GAS_G1000, geraetetyp=not_a_geraetetyp)
+
+        assert "'geraetetyp' must be " in str(excinfo.value)