Repository: XKNX/xknx
Pull request #1470 (2024-06-01 20:15:20)

Change `InternalGroupAddress` attribute `address` to `raw`
<!--
  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.
-->
The same attribute name as `GroupAddress` is used now. The value is now always prepended by `i-`. An equality check of `GroupAddress(1).raw == InternalGroupAddress("i-1").raw` is False because of the "i-" prefix.

Also the equality check of addresses is changed to be ~4x faster.
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
- [x] Tests have been added that prove the fix is effective or that the feature works
- [x] The changes are documented in the changelog (docs/changelog.md)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/XKNX/xknx/pull/1470?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 96.65%. Comparing base [(`c1cabbd`)](https://app.codecov.io/gh/XKNX/xknx/commit/c1cabbdffd31e4b8a43791e6fac5c6931e8afc90?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) to head [(`2e2c6ec`)](https://app.codecov.io/gh/XKNX/xknx/commit/2e2c6ec5f412ac36c2978214b40e4d2b38e45295?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX).

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/XKNX/xknx/pull/1470/graphs/tree.svg?width=650&height=150&src=pr&token=irWbIygS84&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)](https://app.codecov.io/gh/XKNX/xknx/pull/1470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)

```diff
@@           Coverage Diff           @@
##             main    #1470   +/-   ##
=======================================
  Coverage   96.65%   96.65%           
=======================================
  Files         152      152           
  Lines        9965     9966    +1     
=======================================
+ Hits         9632     9633    +1     
  Misses        333      333           
```


| [Files](https://app.codecov.io/gh/XKNX/xknx/pull/1470?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | Coverage Δ | |
|---|---|---|
| [xknx/telegram/address.py](https://app.codecov.io/gh/XKNX/xknx/pull/1470?src=pr&el=tree&filepath=xknx%2Ftelegram%2Faddress.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC90ZWxlZ3JhbS9hZGRyZXNzLnB5) | `100.00% <100.00%> (ø)` | |
| [xknx/telegram/address\_filter.py](https://app.codecov.io/gh/XKNX/xknx/pull/1470?src=pr&el=tree&filepath=xknx%2Ftelegram%2Faddress_filter.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC90ZWxlZ3JhbS9hZGRyZXNzX2ZpbHRlci5weQ==) | `97.72% <100.00%> (ø)` | |

</details>

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 790f34cc..84f3b3ca 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -18,6 +18,10 @@ nav_order: 2
   - 251.600 - DPTColorRGBW - RGBWColor
 - Support dict values with "main" and "sub" keys for `DPTBase.parse_transcoder()`
 
+### Address
+
+- `InternalGroupAddress` attribute `address` is renamed to `raw` to be in line with `GroupAddress` (although still str). Its value has an "i-" prefix.
+
 # 2.12.2 Fix thread leak 2024-03-05
 
 ### Bugfixes
diff --git a/xknx/telegram/address.py b/xknx/telegram/address.py
index 1417f7fb..8f09b16a 100644
--- a/xknx/telegram/address.py
+++ b/xknx/telegram/address.py
@@ -87,7 +87,7 @@ class BaseAddress(ABC):
         Returns `True` if we check against the same subclass and the
         raw Value matches.
         """
-        return self.__hash__() == other.__hash__()
+        return isinstance(other, self.__class__) and self.raw == other.raw
 
     def __hash__(self) -> int:
         """Hash Address so it can be used as dict key."""
@@ -341,10 +341,10 @@ class InternalGroupAddress:
 
     def __init__(self, address: str | InternalGroupAddress) -> None:
         """Initialize InternalGroupAddress class."""
-        self.address: str
+        self.raw: str
 
         if isinstance(address, InternalGroupAddress):
-            self.address = address.address
+            self.raw = address.raw
             return
         if not isinstance(address, str):
             raise CouldNotParseAddress(address, message="Invalid type")
@@ -355,17 +355,18 @@ class InternalGroupAddress:
         if address[1] in "-_":
             prefix_length = 2
 
-        self.address = address[prefix_length:].strip()
-        if not self.address:
+        _raw = address[prefix_length:].strip()
+        if not _raw:
             raise CouldNotParseAddress(address, message="No chars after prefix")
+        self.raw = f"i-{_raw}"
 
     def __str__(self) -> str:
         """Return object as readable string (e.g. 'i-123')."""
-        return f"i-{self.address}"
+        return self.raw
 
     def __repr__(self) -> str:
         """Return object as parsable string."""
-        return f'InternalGroupAddress("{self}")'
+        return f'InternalGroupAddress("{self.raw}")'
 
     def __eq__(self, other: object | None) -> bool:
         """
@@ -374,8 +375,8 @@ class InternalGroupAddress:
         Returns `True` if we check against the same subclass and the
         raw Value matches.
         """
-        return self.__hash__() == other.__hash__()
+        return isinstance(other, self.__class__) and self.raw == other.raw
 
     def __hash__(self) -> int:
         """Hash Address so it can be used as dict key."""
-        return hash((self.__class__, self.address))
+        return hash((self.__class__, self.raw))
diff --git a/xknx/telegram/address_filter.py b/xknx/telegram/address_filter.py
index b9b4e49b..ea2e1d90 100644
--- a/xknx/telegram/address_filter.py
+++ b/xknx/telegram/address_filter.py
@@ -48,7 +48,7 @@ class AddressFilter:
 
     def _parse_pattern(self, pattern: str) -> None:
         if pattern.startswith("i"):
-            self.internal_group_address_pattern = InternalGroupAddress(pattern).address
+            self.internal_group_address_pattern = InternalGroupAddress(pattern).raw
             return
 
         for part in pattern.split("/"):
@@ -72,7 +72,7 @@ class AddressFilter:
             isinstance(address, InternalGroupAddress)
             and self.internal_group_address_pattern
         ):
-            return fnmatch(address.address, self.internal_group_address_pattern)
+            return fnmatch(address.raw, self.internal_group_address_pattern)
 
         return False
 


Test patch:
diff --git a/test/telegram_tests/address_test.py b/test/telegram_tests/address_test.py
index 0448e8e4..27f972f6 100644
--- a/test/telegram_tests/address_test.py
+++ b/test/telegram_tests/address_test.py
@@ -97,21 +97,21 @@ individual_addresses_invalid = [
 ]
 
 internal_group_addresses_valid = {
-    "i 123": "123",
-    "i-123": "123",
-    "i_123": "123",
-    "I 123": "123",
-    "i abc": "abc",
-    "i-abc": "abc",
-    "i_abc": "abc",
-    "I-abc": "abc",
-    "i123": "123",
-    "iabc": "abc",
-    "IABC": "ABC",
-    "i   abc  ": "abc",
-    "i asdf 123 adsf ": "asdf 123 adsf",
-    "i-1/2/3": "1/2/3",
-    InternalGroupAddress("i-123"): "123",
+    "i 123": "i-123",
+    "i-123": "i-123",
+    "i_123": "i-123",
+    "I 123": "i-123",
+    "i abc": "i-abc",
+    "i-abc": "i-abc",
+    "i_abc": "i-abc",
+    "I-abc": "i-abc",
+    "i123": "i-123",
+    "iabc": "i-abc",
+    "IABC": "i-ABC",
+    "i   abc  ": "i-abc",
+    "i asdf 123 adsf ": "i-asdf 123 adsf",
+    "i-1/2/3": "i-1/2/3",
+    InternalGroupAddress("i-123"): "i-123",
 }
 
 internal_group_addresses_invalid = [
@@ -314,7 +314,7 @@ class TestInternalGroupAddress:
     def test_with_valid(self, address_test, address_raw):
         """Test if the class constructor generates valid raw values."""
 
-        assert InternalGroupAddress(address_test).address == address_raw
+        assert InternalGroupAddress(address_test).raw == address_raw
 
     @pytest.mark.parametrize(
         "address_test",