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

Issue:
Implement BO Standorteigenschaften
https://www.bo4e.de/dokumentation/geschaeftsobjekte/bo-standorteigenschaften
- #174 
- #185 
- #209
Implement COM StandorteigenschaftenStrom
https://www.bo4e.de/dokumentation/komponenten/com-standorteigenschaften-strom
for eic see #147 


Discussion / hints:

Fix patch:
diff --git a/src/bo4e/bo/standorteigenschaften.py b/src/bo4e/bo/standorteigenschaften.py
new file mode 100644
index 000000000..522b0224b
--- /dev/null
+++ b/src/bo4e/bo/standorteigenschaften.py
@@ -0,0 +1,57 @@
+"""
+Contains Standorteigenschaften class
+and corresponding marshmallow schema for de-/serialization
+"""
+from typing import List, Optional
+
+import attr
+from marshmallow import fields
+
+from bo4e.bo.geschaeftsobjekt import Geschaeftsobjekt, GeschaeftsobjektSchema
+from bo4e.com.standorteigenschaftenallgemein import StandorteigenschaftenAllgemein, StandorteigenschaftenAllgemeinSchema
+from bo4e.com.standorteigenschaftengas import StandorteigenschaftenGas, StandorteigenschaftenGasSchema
+from bo4e.com.standorteigenschaftenstrom import StandorteigenschaftenStrom, StandorteigenschaftenStromSchema
+from bo4e.enum.botyp import BoTyp
+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 Standorteigenschaften(Geschaeftsobjekt):
+    """
+    Modelliert die regionalen und spartenspezifischen Eigenschaften einer gegebenen Adresse.
+    """
+
+    # required attributes
+    bo_typ: BoTyp = attr.ib(default=BoTyp.STANDORTEIGENSCHAFTEN)
+    #: Allgemeine Eigenschaften
+    eigenschaften_allgemein: StandorteigenschaftenAllgemein = attr.ib(
+        validator=attr.validators.instance_of(StandorteigenschaftenAllgemein)
+    )
+    #: Eigenschaften zur Sparte Strom
+    eigenschaften_strom: List[StandorteigenschaftenStrom] = attr.ib(
+        validator=attr.validators.deep_iterable(
+            member_validator=attr.validators.instance_of(StandorteigenschaftenStrom),
+            iterable_validator=check_list_length_at_least_one,
+        )
+    )
+    # optional attributes
+    #: Eigenschaften zur Sparte Gas
+    eigenschaften_gas: Optional[StandorteigenschaftenGas] = attr.ib(
+        validator=attr.validators.optional(attr.validators.instance_of(StandorteigenschaftenGas)), default=None
+    )
+
+
+class StandorteigenschaftenSchema(GeschaeftsobjektSchema):
+    """
+    Schema for de-/serialization of Standorteigenschaften
+    """
+
+    class_name = Standorteigenschaften
+    # required attributes
+    eigenschaften_allgemein = fields.Nested(StandorteigenschaftenAllgemeinSchema)
+
+    eigenschaften_strom = fields.List(fields.Nested(StandorteigenschaftenStromSchema))
+
+    # optional attributes
+    eigenschaften_gas = fields.Nested(StandorteigenschaftenGasSchema, load_default=None)
diff --git a/src/bo4e/enum/botyp.py b/src/bo4e/enum/botyp.py
index c73ad4736..690f6a550 100644
--- a/src/bo4e/enum/botyp.py
+++ b/src/bo4e/enum/botyp.py
@@ -29,6 +29,7 @@ class BoTyp(StrEnum):
     PREISBLATTUMLAGEN = "PREISBLATTUMLAGEN"
     RECHNUNG = "RECHNUNG"
     REGIONALTARIF = "REGIONALTARIF"
+    STANDORTEIGENSCHAFTEN = "STANDORTEIGENSCHAFTEN"
     TARIFINFO = "TARIFINFO"
     TARIFKOSTEN = "TARIFKOSTEN"
     TARIFPREISBLATT = "TARIFPREISBLATT"


Test patch:
diff --git a/tests/test_standorteigenschaften.py b/tests/test_standorteigenschaften.py
new file mode 100644
index 000000000..ade4f371e
--- /dev/null
+++ b/tests/test_standorteigenschaften.py
@@ -0,0 +1,30 @@
+import pytest  # type:ignore[import]
+
+from bo4e.bo.standorteigenschaften import Standorteigenschaften, StandorteigenschaftenSchema
+from tests.serialization_helper import assert_serialization_roundtrip  # type:ignore[import]
+from tests.test_standorteigenschaftenallgemein import example_standorteigenschaften_allgemein  # type:ignore[import]
+from tests.test_standorteigenschaftengas import example_standorteigenschaften_gas  # type:ignore[import]
+from tests.test_standorteigenschaftenstrom import example_standorteigenschaften_strom  # type:ignore[import]
+
+
+class TestStandorteigenschaften:
+    @pytest.mark.parametrize(
+        "standorteigenschaften",
+        [
+            pytest.param(
+                Standorteigenschaften(
+                    eigenschaften_allgemein=example_standorteigenschaften_allgemein,
+                    eigenschaften_strom=[example_standorteigenschaften_strom],
+                    eigenschaften_gas=example_standorteigenschaften_gas,
+                )
+            ),
+        ],
+    )
+    def test_serialization_roundtrip(self, standorteigenschaften: Standorteigenschaften):
+        assert_serialization_roundtrip(standorteigenschaften, StandorteigenschaftenSchema())
+
+    def test_missing_required_attribute(self):
+        with pytest.raises(TypeError) as excinfo:
+            _ = Standorteigenschaften()
+
+        assert "missing 2 required" in str(excinfo.value)
diff --git a/tests/test_standorteigenschaftenallgemein.py b/tests/test_standorteigenschaftenallgemein.py
index 63fecac8e..e476b95c4 100644
--- a/tests/test_standorteigenschaftenallgemein.py
+++ b/tests/test_standorteigenschaftenallgemein.py
@@ -3,32 +3,30 @@
 from bo4e.com.standorteigenschaftenallgemein import StandorteigenschaftenAllgemein, StandorteigenschaftenAllgemeinSchema
 from tests.serialization_helper import assert_serialization_roundtrip  # type:ignore[import]
 
+example_standorteigenschaften_allgemein = StandorteigenschaftenAllgemein(
+    postleitzahl="66646",
+    ort="Alsweiler",
+    kreisname="St. Wendel",
+    gemeindename="Marpingen",
+    gemeindekennziffer="10 0 46 112",
+    gemeindeeinwohner=9961,
+    bundesland="Saarland",
+)
+example_standorteigenschaften_allgemein_dict = {
+    "postleitzahl": "66646",
+    "ort": "Alsweiler",
+    "kreisname": "St. Wendel",
+    "gemeindename": "Marpingen",
+    "gemeindekennziffer": "10 0 46 112",
+    "gemeindeeinwohner": 9961,
+    "bundesland": "Saarland",
+}
+
 
 class TestStandorteigenschaftenAllgemein:
     @pytest.mark.parametrize(
         "standorteigenschaftenallgemein, expected_json_dict",
-        [
-            pytest.param(
-                StandorteigenschaftenAllgemein(
-                    postleitzahl="66646",
-                    ort="Alsweiler",
-                    kreisname="St. Wendel",
-                    gemeindename="Marpingen",
-                    gemeindekennziffer="10 0 46 112",
-                    gemeindeeinwohner=9961,
-                    bundesland="Saarland",
-                ),
-                {
-                    "postleitzahl": "66646",
-                    "ort": "Alsweiler",
-                    "kreisname": "St. Wendel",
-                    "gemeindename": "Marpingen",
-                    "gemeindekennziffer": "10 0 46 112",
-                    "gemeindeeinwohner": 9961,
-                    "bundesland": "Saarland",
-                },
-            )
-        ],
+        [pytest.param(example_standorteigenschaften_allgemein, example_standorteigenschaften_allgemein_dict)],
     )
     def test_standorteigenschaftenallgemein_serialization_roundtrip(
         self, standorteigenschaftenallgemein: StandorteigenschaftenAllgemein, expected_json_dict: dict
diff --git a/tests/test_standorteigenschaftengas.py b/tests/test_standorteigenschaftengas.py
index 8a93f97bb..10f4fb5af 100644
--- a/tests/test_standorteigenschaftengas.py
+++ b/tests/test_standorteigenschaftengas.py
@@ -4,16 +4,18 @@
 from bo4e.com.standorteigenschaftengas import StandorteigenschaftenGas, StandorteigenschaftenGasSchema
 from tests.serialization_helper import assert_serialization_roundtrip  # type:ignore[import]
 
+example_standorteigenschaften_gas = StandorteigenschaftenGas(
+    netzkontonummern=["GASPOOLNH700xxxx"],
+    marktgebiete=[MarktgebietInfo(marktgebiet="Gaspool", marktgebietcode="37Z701133MH0000B")],
+)
+
 
 class TestStandorteigenschaftenGas:
     @pytest.mark.parametrize(
         "standorteigenschaftengas, expected_json_dict",
         [
             pytest.param(
-                StandorteigenschaftenGas(
-                    netzkontonummern=["GASPOOLNH700xxxx"],
-                    marktgebiete=[MarktgebietInfo(marktgebiet="Gaspool", marktgebietcode="37Z701133MH0000B")],
-                ),
+                example_standorteigenschaften_gas,
                 {
                     "netzkontonummern": ["GASPOOLNH700xxxx"],
                     "marktgebiete": [{"marktgebiet": "Gaspool", "marktgebietcode": "37Z701133MH0000B"}],
diff --git a/tests/test_standorteigenschaftenstrom.py b/tests/test_standorteigenschaftenstrom.py
index 54ba330ed..429cb3419 100644
--- a/tests/test_standorteigenschaftenstrom.py
+++ b/tests/test_standorteigenschaftenstrom.py
@@ -3,17 +3,19 @@
 from bo4e.com.standorteigenschaftenstrom import StandorteigenschaftenStrom, StandorteigenschaftenStromSchema
 from tests.serialization_helper import assert_serialization_roundtrip  # type:ignore[import]
 
+example_standorteigenschaften_strom = StandorteigenschaftenStrom(
+    regelzone="Transnet BW",
+    bilanzierungsgebiet_eic="11YW-ALBSTADT--5",
+    regelzone_eic="10YDE-ENBW-----N",
+)
+
 
 class TestStandorteigenschaftenStrom:
     @pytest.mark.parametrize(
         "standorteigenschaften_strom, expected_json_dict",
         [
             pytest.param(
-                StandorteigenschaftenStrom(
-                    regelzone="Transnet BW",
-                    bilanzierungsgebiet_eic="11YW-ALBSTADT--5",
-                    regelzone_eic="10YDE-ENBW-----N",
-                ),
+                example_standorteigenschaften_strom,
                 {
                     "regelzone": "Transnet BW",
                     "bilanzierungsgebietEic": "11YW-ALBSTADT--5",