Repository: XKNX/xknx
Pull request #1418 (2024-02-21 21:08:21)

Mode detailed address parsing error messages
<!--
  You are awesome! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template!.
-->
## Description
<!--
Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
-->


Fixes # (issue)

## Type of change
<!--
Please tick the applicable options.
NOTE: Ticking multiple options most likely indicates
that your change is to big and it is suggested to split it into several smaller PRs.
-->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [x] Code quality improvements to existing code or addition of tests

## Checklist

- [ ] The documentation has been adjusted accordingly
- [ ] Tests have been added that prove the fix is effective or that the feature works
- [ ] The changes are documented in the changelog (docs/changelog.md)


Patch:
diff --git a/requirements/production.txt b/requirements/production.txt
index efa6f40a..7ee1ae6e 100644
--- a/requirements/production.txt
+++ b/requirements/production.txt
@@ -1,3 +1,3 @@
 async-timeout==4.0.2;python_version<"3.11"
-cryptography==42.0.3
+cryptography==42.0.4
 ifaddr==0.2.0
diff --git a/xknx/telegram/address.py b/xknx/telegram/address.py
index 3a0e6ef0..e70cb542 100644
--- a/xknx/telegram/address.py
+++ b/xknx/telegram/address.py
@@ -31,6 +31,9 @@ DeviceGroupAddress = Union["GroupAddress", "InternalGroupAddress"]
 Self = TypeVar("Self", bound="BaseAddress")
 
 
+INVALID_PREFIX_MESSAGE = "Invalid prefix for internal group address"
+
+
 def parse_device_group_address(
     address: DeviceAddressableType,
 ) -> DeviceGroupAddress:
@@ -39,7 +42,12 @@ def parse_device_group_address(
         group_address = GroupAddress(address)  # type: ignore[arg-type]  # InternalGroupAddress will raise
     except CouldNotParseAddress as ex:
         if isinstance(address, (str, InternalGroupAddress)):
-            return InternalGroupAddress(address)
+            try:
+                return InternalGroupAddress(address)
+            except CouldNotParseAddress as internal_ex:
+                # prefer to raise original exception from GroupAddress
+                if internal_ex.message != INVALID_PREFIX_MESSAGE:
+                    raise internal_ex
         raise ex
 
     if group_address.raw == 0:
@@ -110,7 +118,9 @@ class IndividualAddress(BaseAddress):
             raise CouldNotParseAddress(address, message="Invalid type")
 
         if not 0 <= self.raw <= 65535:
-            raise CouldNotParseAddress(address, message="Address out of range")
+            raise CouldNotParseAddress(
+                address, message="Address out of range (0..65535)"
+            )
 
     def __string_to_int(self, address: str) -> int:
         """
@@ -129,11 +139,17 @@ class IndividualAddress(BaseAddress):
         main = int(match.group("main"))
         line = int(match.group("line"))
         if area > self.MAX_AREA:
-            raise CouldNotParseAddress(address, message="Area part out of range")
+            raise CouldNotParseAddress(
+                address, message=f"Area part out of range (0..{self.MAX_AREA})"
+            )
         if main > self.MAX_MAIN:
-            raise CouldNotParseAddress(address, message="Line part out of range")
+            raise CouldNotParseAddress(
+                address, message=f"Line part out of range (0..{self.MAX_MAIN})"
+            )
         if line > self.MAX_LINE:
-            raise CouldNotParseAddress(address, message="Device part out of range")
+            raise CouldNotParseAddress(
+                address, message=f"Device part out of range (0..{self.MAX_LINE})"
+            )
         return (area << 12) + (main << 8) + line
 
     @property
@@ -216,7 +232,9 @@ class GroupAddress(BaseAddress):
             raise CouldNotParseAddress(address, message="Invalid type")
 
         if not 0 <= self.raw <= 65535:
-            raise CouldNotParseAddress(address, message="Address out of range")
+            raise CouldNotParseAddress(
+                address, message="Address out of range (0..65535)"
+            )
 
     def __string_to_int(self, address: str) -> int:
         """
@@ -237,14 +255,22 @@ class GroupAddress(BaseAddress):
         )
         sub = int(match.group("sub"))
         if main > self.MAX_MAIN:
-            raise CouldNotParseAddress(address, message="Main group out of range")
+            raise CouldNotParseAddress(
+                address, message=f"Main group out of range (0..{self.MAX_MAIN})"
+            )
         if middle is not None:
             if middle > self.MAX_MIDDLE:
-                raise CouldNotParseAddress(address, message="Middle group out of range")
+                raise CouldNotParseAddress(
+                    address, message=f"Middle group out of range (0..{self.MAX_MIDDLE})"
+                )
             if sub > self.MAX_SUB_LONG:
-                raise CouldNotParseAddress(address, message="Sub group out of range")
+                raise CouldNotParseAddress(
+                    address, message=f"Sub group out of range (0..{self.MAX_SUB_LONG})"
+                )
         elif sub > self.MAX_SUB_SHORT:
-            raise CouldNotParseAddress(address, message="Sub group out of range")
+            raise CouldNotParseAddress(
+                address, message=f"Sub group out of range (0..{self.MAX_SUB_SHORT})"
+            )
         return (
             (main << 11) + (middle << 8) + sub
             if middle is not None
@@ -324,7 +350,7 @@ class InternalGroupAddress:
 
         prefix_length = 1
         if len(address) < 2 or address[0].lower() != "i":
-            raise CouldNotParseAddress(address, message="Invalid prefix")
+            raise CouldNotParseAddress(address, message=INVALID_PREFIX_MESSAGE)
         if address[1] in "-_":
             prefix_length = 2
 


Test patch:
diff --git a/test/telegram_tests/address_test.py b/test/telegram_tests/address_test.py
index 44c74e77..a2e2ff2e 100644
--- a/test/telegram_tests/address_test.py
+++ b/test/telegram_tests/address_test.py
@@ -376,3 +376,8 @@ class TestParseDestinationAddress:
         """Test if the function raises CouldNotParseAddress on invalid values."""
         with pytest.raises(CouldNotParseAddress):
             parse_device_group_address(address_test)
+
+    def test_parse_device_invalid_group_address_message(self):
+        """Test if the error message is from GroupAddress, not InternalGroupAddress for strings."""
+        with pytest.raises(CouldNotParseAddress, match=r".*Sub group out of range.*"):
+            parse_device_group_address("0/0/700")