Repository: XKNX/xknx
Pull request #1487 (2024-06-15 19:00:08)

Use xknx.telegrams.put_nowait instead of awaiting
<!--
  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)
- [x] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Code quality improvements to existing code or addition of tests

## Checklist

- [x] 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/1487?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.62%. Comparing base [(`a53cb0e`)](https://app.codecov.io/gh/XKNX/xknx/commit/a53cb0ef66166a42d945d6d22be5698d349e6c73?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) to head [(`70c6cba`)](https://app.codecov.io/gh/XKNX/xknx/commit/70c6cba041abc993b0307c019f6a11fd0812f29f?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/1487/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/1487?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)

```diff
@@           Coverage Diff           @@
##             main    #1487   +/-   ##
=======================================
  Coverage   96.62%   96.62%           
=======================================
  Files         154      154           
  Lines       10027    10027           
=======================================
  Hits         9689     9689           
  Misses        338      338           
```


| [Files](https://app.codecov.io/gh/XKNX/xknx/pull/1487?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | Coverage Δ | |
|---|---|---|
| [xknx/core/telegram\_queue.py](https://app.codecov.io/gh/XKNX/xknx/pull/1487?src=pr&el=tree&filepath=xknx%2Fcore%2Ftelegram_queue.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9jb3JlL3RlbGVncmFtX3F1ZXVlLnB5) | `98.30% <100.00%> (ø)` | |
| [xknx/core/value\_reader.py](https://app.codecov.io/gh/XKNX/xknx/pull/1487?src=pr&el=tree&filepath=xknx%2Fcore%2Fvalue_reader.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9jb3JlL3ZhbHVlX3JlYWRlci5weQ==) | `100.00% <100.00%> (ø)` | |
| [xknx/remote\_value/remote\_value.py](https://app.codecov.io/gh/XKNX/xknx/pull/1487?src=pr&el=tree&filepath=xknx%2Fremote_value%2Fremote_value.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9yZW1vdGVfdmFsdWUvcmVtb3RlX3ZhbHVlLnB5) | `98.60% <100.00%> (ø)` | |
| [xknx/tools/group\_communication.py](https://app.codecov.io/gh/XKNX/xknx/pull/1487?src=pr&el=tree&filepath=xknx%2Ftools%2Fgroup_communication.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC90b29scy9ncm91cF9jb21tdW5pY2F0aW9uLnB5) | `98.00% <100.00%> (ø)` | |

</details>

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 796e18af..38508b34 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -8,10 +8,17 @@ nav_order: 2
 
 # Unreleased changes
 
-### General
+### Breaking changes
 
 - Drop support for Python 3.9
 - Change callback signatures from awaitable to callable in `XKNX.device_updated_cb`, TelegramQueue, Device, Devices and RemoteValue.
+- Remove `async` from functions / methods (nothing has to be awaited there)
+  - Tools:  `group_value_write`, `group_value_response` and `group_value_read`
+  - RemoteValue: `.set`, `.respond`, `.process` and `.update_value`
+  - ValueReader: `.send_group_read`
+
+### Features
+
 - Added eager telegram data decoding for GroupValueWrite / GroupValueResponse Telegrams. DPTs for group addresses can be set using `xknx.group_address_dpt.set()`. `Telegram` has a new attribute `decoded_data` which is set when a decoder was found.
 
 ### Devices
@@ -39,7 +46,6 @@ nav_order: 2
 - Convert Telegram and APCI to dataclasses. `Telegram` is not hashable anymore.
 - Use `asyncio.gather` where applicable (mostly device telegram process pipeline)
 - RemoteValue instances use pre-decoded data from Telegrams if available and `dpt_class` for is set - otherwise they decode the data themselves in `from_knx` like before.
-- Change `RemoteValue.after_update_cb` from awaitable to callable. Remove `async` from `RemoteValue.set`, `.respond`, `.process` and `.update_value` (nothing has to be awaited there).
 
 # 2.12.2 Fix thread leak 2024-03-05
 
diff --git a/docs/index.md b/docs/index.md
index 3ba23dd1..d6b17152 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -45,11 +45,11 @@ from xknx.tools import group_value_write
 async def main():
     async with XKNX() as xknx:
         # send a binary Telegram
-        await group_value_write(xknx, "1/2/3", True)
+        group_value_write(xknx, "1/2/3", True)
         # send a generic 1-byte Telegram
-        await group_value_write(xknx, "1/2/4", [0x80])
+        group_value_write(xknx, "1/2/4", [0x80])
         # send a Telegram with an encoded value
-        await group_value_write(xknx, "1/2/4", 50, value_type="percent")
+        group_value_write(xknx, "1/2/4", 50, value_type="percent")
 
 asyncio.run(main())
 ```
diff --git a/examples/example_secure.py b/examples/example_secure.py
index 79108e23..5993d403 100644
--- a/examples/example_secure.py
+++ b/examples/example_secure.py
@@ -28,7 +28,7 @@ async def main() -> None:
 
     await xknx.start()
     print("Tunnel connected")
-    await group_value_write(xknx, "1/0/32", True)
+    group_value_write(xknx, "1/0/32", True)
     await asyncio.sleep(5)
     await xknx.stop()
 
diff --git a/examples/example_send_telegrams.py b/examples/example_send_telegrams.py
index 34803a74..7f62c6b2 100644
--- a/examples/example_send_telegrams.py
+++ b/examples/example_send_telegrams.py
@@ -15,9 +15,9 @@ async def main() -> None:
     """Connect to KNX bus and send telegrams."""
     async with XKNX() as xknx:
         # send a DPT 9.001 temperature value
-        await group_value_write(xknx, "5/1/20", 21.7, value_type="temperature")
+        group_value_write(xknx, "5/1/20", 21.7, value_type="temperature")
         # send a response DPT 1 binary value
-        await group_value_response(xknx, "5/1/20", True)
+        group_value_response(xknx, "5/1/20", True)
 
 
 asyncio.run(main())
diff --git a/xknx/core/telegram_queue.py b/xknx/core/telegram_queue.py
index 376e391c..e749192b 100644
--- a/xknx/core/telegram_queue.py
+++ b/xknx/core/telegram_queue.py
@@ -109,7 +109,7 @@ class TelegramQueue:
         """Stop telegram queue."""
         logger.debug("Stopping TelegramQueue")
         # If a None object is pushed to the queue, the queue stops
-        await self.xknx.telegrams.put(None)
+        self.xknx.telegrams.put_nowait(None)
         if self._consumer_task is not None:
             await self._consumer_task
 
diff --git a/xknx/core/value_reader.py b/xknx/core/value_reader.py
index ed1fffdc..f15d6175 100644
--- a/xknx/core/value_reader.py
+++ b/xknx/core/value_reader.py
@@ -48,7 +48,7 @@ class ValueReader:
             group_addresses=[self.group_address],
             match_for_outgoing=True,
         )
-        await self.send_group_read()
+        self.send_group_read()
 
         try:
             async with asyncio_timeout(self.timeout_in_seconds):
@@ -67,14 +67,14 @@ class ValueReader:
 
         return None
 
-    async def send_group_read(self) -> None:
+    def send_group_read(self) -> None:
         """Send group read."""
         telegram = Telegram(
             destination_address=self.group_address,
             payload=GroupValueRead(),
             source_address=self.xknx.current_address,
         )
-        await self.xknx.telegrams.put(telegram)
+        self.xknx.telegrams.put_nowait(telegram)
 
     def telegram_received(self, telegram: Telegram) -> None:
         """Test if telegram has correct group address and trigger event."""
diff --git a/xknx/remote_value/remote_value.py b/xknx/remote_value/remote_value.py
index 16e30ff6..3a9219c6 100644
--- a/xknx/remote_value/remote_value.py
+++ b/xknx/remote_value/remote_value.py
@@ -272,7 +272,7 @@ class RemoteValue(ABC, Generic[ValueT]):
                         self.feature_name,
                     )
             else:
-                await value_reader.send_group_read()
+                value_reader.send_group_read()
 
     @property
     def unit_of_measurement(self) -> str | None:
diff --git a/xknx/tools/group_communication.py b/xknx/tools/group_communication.py
index f83127b6..bb38f478 100644
--- a/xknx/tools/group_communication.py
+++ b/xknx/tools/group_communication.py
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
 logger = logging.getLogger("xknx.tools")
 
 
-async def group_value_read(
+def group_value_read(
     xknx: XKNX,
     group_address: DeviceAddressableType,
 ) -> None:
@@ -27,10 +27,10 @@ async def group_value_read(
     )
 
     logger.debug("Sending GroupValueRead telegram to %s", group_address)
-    await xknx.telegrams.put(telegram)
+    xknx.telegrams.put_nowait(telegram)
 
 
-async def group_value_response(
+def group_value_response(
     xknx: XKNX,
     group_address: DeviceAddressableType,
     value: Any,
@@ -48,10 +48,10 @@ async def group_value_response(
         value_type or "raw",
         group_address,
     )
-    await xknx.telegrams.put(telegram)
+    xknx.telegrams.put_nowait(telegram)
 
 
-async def group_value_write(
+def group_value_write(
     xknx: XKNX,
     group_address: DeviceAddressableType,
     value: Any,
@@ -69,7 +69,7 @@ async def group_value_write(
         value_type or "raw",
         group_address,
     )
-    await xknx.telegrams.put(telegram)
+    xknx.telegrams.put_nowait(telegram)
 
 
 async def read_group_value(


Test patch:
diff --git a/test/core_tests/group_address_dpt_test.py b/test/core_tests/group_address_dpt_test.py
index cf28ecf6..f0f66fc7 100644
--- a/test/core_tests/group_address_dpt_test.py
+++ b/test/core_tests/group_address_dpt_test.py
@@ -22,14 +22,14 @@ async def test_group_address_dpt_in_telegram_queue(xknx_no_interface):
     telegram_callback = Mock()
     xknx.telegram_queue.register_telegram_received_cb(telegram_callback)
     async with xknx:
-        await xknx.telegrams.put(telegram)
+        xknx.telegrams.put_nowait(telegram)
         await xknx.telegrams.join()
         assert telegram_callback.call_count == 1
         test_telegram = telegram_callback.call_args[0][0]
         assert test_telegram.decoded_data is None
 
         xknx.group_address_dpt.set({GroupAddress("1/2/3"): {"main": 5, "sub": 1}})
-        await xknx.telegrams.put(telegram)
+        xknx.telegrams.put_nowait(telegram)
         await xknx.telegrams.join()
         assert telegram_callback.call_count == 2
         test_telegram = telegram_callback.call_args[0][0]
@@ -37,7 +37,7 @@ async def test_group_address_dpt_in_telegram_queue(xknx_no_interface):
         assert test_telegram.decoded_data.transcoder is DPTScaling
         assert test_telegram.decoded_data.value == 50
         # do nothing for read-telegrams
-        await xknx.telegrams.put(read_telegram)
+        xknx.telegrams.put_nowait(read_telegram)
         await xknx.telegrams.join()
         assert telegram_callback.call_count == 3
         test_telegram = telegram_callback.call_args[0][0]
@@ -58,7 +58,7 @@ async def test_get_invalid_payload(logger_warning_mock, xknx_no_interface):
         payload=apci.GroupValueWrite(DPTArray((0x01, 0x02))),  # wrong payload size
     )
     async with xknx:
-        await xknx.telegrams.put(telegram)
+        xknx.telegrams.put_nowait(telegram)
         await xknx.telegrams.join()
         assert telegram_callback.call_count == 1
         test_telegram = telegram_callback.call_args[0][0]
diff --git a/test/core_tests/value_reader_test.py b/test/core_tests/value_reader_test.py
index 4c095ac8..94542c4e 100644
--- a/test/core_tests/value_reader_test.py
+++ b/test/core_tests/value_reader_test.py
@@ -88,7 +88,7 @@ class TestValueReader:
         xknx = XKNX()
         value_reader = ValueReader(xknx, GroupAddress("0/0/1"))
 
-        await value_reader.send_group_read()
+        value_reader.send_group_read()
         assert xknx.telegrams.qsize() == 1
         telegram = xknx.telegrams.get_nowait()
         assert telegram == Telegram(
diff --git a/test/devices_tests/sensor_expose_loop_test.py b/test/devices_tests/sensor_expose_loop_test.py
index 87d14c4f..4432d389 100644
--- a/test/devices_tests/sensor_expose_loop_test.py
+++ b/test/devices_tests/sensor_expose_loop_test.py
@@ -1,6 +1,6 @@
 """Unit test for Sensor and ExposeSensor objects."""
 
-from unittest.mock import AsyncMock, call
+from unittest.mock import AsyncMock, Mock, call
 
 import pytest
 
@@ -325,7 +325,7 @@ class TestBinarySensorInternalGroupAddressExposeLoop:
         xknx = XKNX()
         xknx.cemi_handler = AsyncMock()
 
-        telegram_callback = AsyncMock()
+        telegram_callback = Mock()
         xknx.telegram_queue.register_telegram_received_cb(
             telegram_callback,
             address_filters=[AddressFilter("i-test")],
diff --git a/test/tools_tests/tools_test.py b/test/tools_tests/tools_test.py
index 8279fb16..0085cb68 100644
--- a/test/tools_tests/tools_test.py
+++ b/test/tools_tests/tools_test.py
@@ -17,7 +17,7 @@ from xknx.tools import (
 )
 
 
-async def test_group_value_read():
+def test_group_value_read():
     """Test group_value_read."""
     xknx = XKNX()
     group_address = "1/2/3"
@@ -26,12 +26,12 @@ async def test_group_value_read():
         destination_address=GroupAddress(group_address),
         payload=apci.GroupValueRead(),
     )
-    await group_value_read(xknx, "1/2/3")
+    group_value_read(xknx, "1/2/3")
     assert xknx.telegrams.qsize() == 1
     assert xknx.telegrams.get_nowait() == read
 
 
-async def test_group_value_response():
+def test_group_value_response():
     """Test group_value_response."""
     xknx = XKNX()
     group_address = "1/2/3"
@@ -40,7 +40,7 @@ async def test_group_value_response():
         destination_address=GroupAddress(group_address),
         payload=apci.GroupValueResponse(DPTBinary(1)),
     )
-    await group_value_response(xknx, "1/2/3", True)
+    group_value_response(xknx, "1/2/3", True)
     assert xknx.telegrams.qsize() == 1
     assert xknx.telegrams.get_nowait() == response
 
@@ -57,7 +57,7 @@ async def test_group_value_response():
         (DPTBinary(1), None, DPTBinary(1)),
     ],
 )
-async def test_group_value_write(value, value_type, expected):
+def test_group_value_write(value, value_type, expected):
     """Test group_value_write."""
     xknx = XKNX()
     group_address = "1/2/3"
@@ -66,7 +66,7 @@ async def test_group_value_write(value, value_type, expected):
         destination_address=GroupAddress(group_address),
         payload=apci.GroupValueWrite(expected),
     )
-    await group_value_write(xknx, "1/2/3", value, value_type=value_type)
+    group_value_write(xknx, "1/2/3", value, value_type=value_type)
     assert xknx.telegrams.qsize() == 1
     assert xknx.telegrams.get_nowait() == write
 
@@ -79,11 +79,11 @@ async def test_group_value_write(value, value_type, expected):
         (101, "percent", ConversionError),  # too big
     ],
 )
-async def test_group_value_write_invalid(value, value_type, error_type):
+def test_group_value_write_invalid(value, value_type, error_type):
     """Test group_value_write."""
     xknx = XKNX()
     with pytest.raises(error_type):
-        await group_value_write(xknx, "1/2/3", value, value_type=value_type)
+        group_value_write(xknx, "1/2/3", value, value_type=value_type)
 
 
 @pytest.mark.parametrize(
@@ -115,11 +115,10 @@ async def test_read_group_value(value_reader_read_mock, value, value_type, expec
     assert response_value == value
 
 
-async def test_tools_with_internal_addresses():
+async def test_tools_with_internal_addresses(xknx_no_interface):
     """Test tools using internal addresses."""
-    with patch("xknx.io.knxip_interface.KNXIPInterface.start"):
-        xknx = XKNX()
-        await xknx.start()
+    xknx = xknx_no_interface
+    await xknx.start()
 
     internal_address = "i-test"
     test_type = "1byte_unsigned"
@@ -133,7 +132,7 @@ async def test_tools_with_internal_addresses():
     xknx.devices.async_add(number)
 
     assert number.resolve_state() is None
-    await group_value_write(xknx, internal_address, 1, value_type=test_type)
+    group_value_write(xknx, internal_address, 1, value_type=test_type)
     await xknx.telegrams.join()
     assert number.resolve_state() == 1