Parcourir la source

Eliminate remaining explicit primary and secondary entity references

Initially the integration was designed with a primary entity for each
device, with secondary entities designed to be optionally enabled
according to user preference.

Since then, HA has added entity categories to take care of what is
primary and secondary, which is more flexible, as it allows multiple
primary functions per device, and also made it easier to hide and
disable unwanted entities from the UI, so we dropped the configuration
options for secondary entities, mainly to reduce the effort needed to
maintain translations.

So the distinction between primary entity and secondary entities
doesn't really serve any purpose any more. A few months ago an
all_entities() method was added to simplify all the places that
do the same processing on primary entity and secondary entities.
Locations in the main flow of code were updated to use it, but some
migration, test and utility code was still left using the old methods.

This finishes the task, ready for deprecation of the primary/secondary
distinction.
Jason Rumney il y a 1 an
Parent
commit
aac2b5d1b7

+ 13 - 23
custom_components/tuya_local/__init__.py

@@ -165,11 +165,9 @@ async def async_migrate_entry(hass, entry: ConfigEntry):
         @callback
         def update_unique_id(entity_entry):
             """Update the unique id of an entity entry."""
-            e = conf_file.primary_entity
-            if e.entity != entity_entry.platform:
-                for e in conf_file.secondary_entities():
-                    if e.entity == entity_entry.platform:
-                        break
+            for e in conf_file.all_entities():
+                if e.entity == entity_entry.platform:
+                    break
             if e.entity == entity_entry.platform:
                 new_id = e.unique_id(old_id)
                 if new_id != old_id:
@@ -256,19 +254,13 @@ async def async_migrate_entry(hass, entry: ConfigEntry):
             """Update the unique id of an entity entry."""
             old_id = entity_entry.unique_id
             platform = entity_entry.entity_id.split(".", 1)[0]
-            e = conf_file.primary_entity
-            if e.name:
-                expect_id = f"{device_id}-{slugify(e.name)}"
-            else:
-                expect_id = device_id
-            if e.entity != platform or expect_id != old_id:
-                for e in conf_file.secondary_entities():
-                    if e.name:
-                        expect_id = f"{device_id}-{slugify(e.name)}"
-                    else:
-                        expect_id = device_id
-                    if e.entity == platform and expect_id == old_id:
-                        break
+            for e in conf_file.all_entities():
+                if e.name:
+                    expect_id = f"{device_id}-{slugify(e.name)}"
+                else:
+                    expect_id = device_id
+                if e.entity == platform and expect_id == old_id:
+                    break
 
             if e.entity == platform and expect_id == old_id:
                 new_id = e.unique_id(device_id)
@@ -312,11 +304,9 @@ async def async_migrate_entry(hass, entry: ConfigEntry):
             # if unique_id ends with platform name, then this may have
             # changed with the addition of device_class.
             if old_id.endswith(platform):
-                e = conf_file.primary_entity
-                if e.entity != platform or e.name:
-                    for e in conf_file.secondary_entities():
-                        if e.entity == platform and not e.name:
-                            break
+                for e in conf_file.all_entities():
+                    if e.entity == platform and not e.name:
+                        break
                 if e.entity == platform and not e.name:
                     new_id = e.unique_id(device_id)
                     if new_id != old_id:

+ 22 - 14
tests/test_device_config.py

@@ -506,13 +506,13 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
                 parsed._config.get("primary_entity"),
                 f"primary_entity missing from {cfg}",
             )
-            self.check_entity(parsed.primary_entity, cfg)
-            entities.append(parsed.primary_entity.config_id)
-            secondary = False
-            for entity in parsed.secondary_entities():
-                secondary = True
+            count = 0
+            for entity in parsed.all_entities():
                 self.check_entity(entity, cfg)
                 entities.append(entity.config_id)
+                count += 1
+            assert count > 0, f"No entities found in {cfg}"
+
             # check entities are unique
             self.assertCountEqual(
                 entities,
@@ -521,7 +521,7 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
             )
 
             # If there are no secondary entities, check that it is intended
-            if not secondary:
+            if count == 1:
                 for key in parsed._config.keys():
                     self.assertFalse(
                         key.startswith("sec"),
@@ -580,25 +580,33 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
     def test_entity_find_unknown_dps_fails(self):
         """Test that finding a dps that doesn't exist fails."""
         cfg = get_config("kogan_switch")
-        non_existing = cfg.primary_entity.find_dps("missing")
-        self.assertIsNone(non_existing)
+        for entity in cfg.all_entities():
+            non_existing = entity.find_dps("missing")
+            self.assertIsNone(non_existing)
+            break
 
     async def test_dps_async_set_readonly_value_fails(self):
         """Test that setting a readonly dps fails."""
         mock_device = MagicMock()
         cfg = get_config("aquatech_x6_water_heater")
-        temp = cfg.primary_entity.find_dps("temperature")
-        with self.assertRaises(TypeError):
-            await temp.async_set_value(mock_device, 20)
+        for entity in cfg.all_entities():
+            if entity.entity == "climate":
+                temp = entity.find_dps("temperature")
+                with self.assertRaises(TypeError):
+                    await temp.async_set_value(mock_device, 20)
+                break
 
     def test_dps_values_is_empty_with_no_mapping(self):
         """
-        Test that a dps with no mapping returns None as its possible values
+        Test that a dps with no mapping returns empty list for possible values
         """
         mock_device = MagicMock()
         cfg = get_config("goldair_gpph_heater")
-        temp = cfg.primary_entity.find_dps("current_temperature")
-        self.assertEqual(temp.values(mock_device), [])
+        for entity in cfg.all_entities():
+            if entity.entity == "climate":
+                temp = entity.find_dps("temperature")
+                self.assertEqual(temp.values(mock_device), [])
+                break
 
     def test_config_returned(self):
         """Test that config file is returned by config"""

+ 1 - 5
util/best_match.py

@@ -25,11 +25,7 @@ def main() -> int:
     for m in best_matches:
         dps_seen = set(dps.keys())
         print(f"{m.config_type} matched {m.match_quality(dps)}%")
-        print(f"  {m.primary_entity.config_id}:")
-        for dp in m.primary_entity.dps():
-            dps_seen.discard(dp.id)
-            print(f"   {dp.name}: {dp.get_value(device)}")
-        for entity in m.secondary_entities():
+        for entity in m.all_entities():
             print(f"  {entity.config_id}:")
             for dp in entity.dps():
                 dps_seen.discard(dp.id)

+ 1 - 2
util/catalog.py

@@ -18,8 +18,7 @@ def main() -> int:
     print("Catalog================")
     for config in available_configs():
         device = TuyaDeviceConfig(config)
-        print(f"{config}: {device.primary_entity.config_id}")
-        for entity in device.secondary_entities():
+        for entity in device.all_entities():
             print(f"{config}: {entity.config_id}")
 
 

+ 1 - 5
util/config_match.py

@@ -15,11 +15,7 @@ def main() -> int:
     for match in possible_matches(dps):
         dps_seen = set(dps.keys())
         print(f"{match.config_type} matched {match.match_quality(dps)}%")
-        print(f"  {match.primary_entity.config_id}:")
-        for dp in match.primary_entity.dps():
-            dps_seen.discard(dp.id)
-            print(f"   {dp.name}: {dp.get_value(device)}")
-        for entity in match.secondary_entities():
+        for entity in match.all_entities():
             print(f"  {entity.config_id}:")
             for dp in entity.dps():
                 dps_seen.discard(dp.id)

+ 1 - 18
util/match_against.py

@@ -15,24 +15,7 @@ def main() -> int:
     if config is None:
         print(f"No config could be loaded for {sys.argv[1]}")
         return 1
-    print(f"{config.primary_entity.config_id}:")
-    for dp in config.primary_entity.dps():
-        if dp.id not in dps.keys():
-            print(f"   {dp.name} missing from data")
-            if not dp.optional:
-                print(f">> dp {dp.id} is REQUIRED!!!!")
-        elif not _typematch(dp.type, dps.get(dp.id)):
-            print(
-                f">> {dp.name} type MISMATCH, expected {dp.type.__name__}, got {dps.get(dp.id)}!!!"
-            )
-        else:
-            values = dp.values(device)
-            if values:
-                values = f" from {values}"
-            else:
-                values = ""
-            print(f"   {dp.name}: {dp.get_value(device)}{values}")
-    for entity in config.secondary_entities():
+    for entity in config.all_entities():
         print(f"{entity.config_id}:")
         for dp in entity.dps():
             if dp.id not in dps.keys():