PyPlumIO 0.5.52__tar.gz → 0.5.54__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. {pyplumio-0.5.52 → pyplumio-0.5.54}/.pre-commit-config.yaml +2 -2
  2. {pyplumio-0.5.52 → pyplumio-0.5.54}/PKG-INFO +10 -10
  3. {pyplumio-0.5.52 → pyplumio-0.5.54}/PyPlumIO.egg-info/PKG-INFO +10 -10
  4. {pyplumio-0.5.52 → pyplumio-0.5.54}/PyPlumIO.egg-info/SOURCES.txt +0 -2
  5. {pyplumio-0.5.52 → pyplumio-0.5.54}/PyPlumIO.egg-info/requires.txt +9 -9
  6. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/_version.py +16 -3
  7. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/connection.py +1 -1
  8. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/data_types.py +7 -0
  9. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/devices/__init__.py +2 -1
  10. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/devices/ecomax.py +1 -1
  11. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/filters.py +9 -1
  12. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/frames/__init__.py +13 -0
  13. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/parameters/__init__.py +5 -1
  14. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/stream.py +39 -45
  15. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/utils.py +26 -2
  16. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyproject.toml +9 -9
  17. {pyplumio-0.5.52 → pyplumio-0.5.54}/requirements.txt +1 -1
  18. pyplumio-0.5.54/requirements_test.txt +13 -0
  19. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/devices/test_ecomax.py +2 -2
  20. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/parameters/test_init.py +9 -0
  21. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/test_connection.py +3 -4
  22. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/test_data_types.py +7 -0
  23. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/test_filters.py +10 -0
  24. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/test_stream.py +106 -79
  25. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/test_utils.py +27 -0
  26. pyplumio-0.5.52/pyplumio/helpers/timeout.py +0 -32
  27. pyplumio-0.5.52/requirements_test.txt +0 -13
  28. pyplumio-0.5.52/tests/helpers/test_timeout.py +0 -32
  29. {pyplumio-0.5.52 → pyplumio-0.5.54}/.gitattributes +0 -0
  30. {pyplumio-0.5.52 → pyplumio-0.5.54}/.github/CODE_OF_CONDUCT.md +0 -0
  31. {pyplumio-0.5.52 → pyplumio-0.5.54}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  32. {pyplumio-0.5.52 → pyplumio-0.5.54}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  33. {pyplumio-0.5.52 → pyplumio-0.5.54}/.github/dependabot.yml +0 -0
  34. {pyplumio-0.5.52 → pyplumio-0.5.54}/.github/workflows/ci.yml +0 -0
  35. {pyplumio-0.5.52 → pyplumio-0.5.54}/.github/workflows/codeql.yml +0 -0
  36. {pyplumio-0.5.52 → pyplumio-0.5.54}/.github/workflows/deploy.yml +0 -0
  37. {pyplumio-0.5.52 → pyplumio-0.5.54}/.github/workflows/documentation.yml +0 -0
  38. {pyplumio-0.5.52 → pyplumio-0.5.54}/.gitignore +0 -0
  39. {pyplumio-0.5.52 → pyplumio-0.5.54}/.qlty/qlty.toml +0 -0
  40. {pyplumio-0.5.52 → pyplumio-0.5.54}/.vscode/settings.json +0 -0
  41. {pyplumio-0.5.52 → pyplumio-0.5.54}/LICENSE +0 -0
  42. {pyplumio-0.5.52 → pyplumio-0.5.54}/MANIFEST.in +0 -0
  43. {pyplumio-0.5.52 → pyplumio-0.5.54}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  44. {pyplumio-0.5.52 → pyplumio-0.5.54}/PyPlumIO.egg-info/top_level.txt +0 -0
  45. {pyplumio-0.5.52 → pyplumio-0.5.54}/README.md +0 -0
  46. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/Makefile +0 -0
  47. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/make.bat +0 -0
  48. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/callbacks.rst +0 -0
  49. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/conf.py +0 -0
  50. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/connecting.rst +0 -0
  51. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/frames.rst +0 -0
  52. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/index.rst +0 -0
  53. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/mixers_thermostats.rst +0 -0
  54. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/protocol.rst +0 -0
  55. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/reading.rst +0 -0
  56. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/schedules.rst +0 -0
  57. {pyplumio-0.5.52 → pyplumio-0.5.54}/docs/source/writing.rst +0 -0
  58. {pyplumio-0.5.52 → pyplumio-0.5.54}/images/ecomax.png +0 -0
  59. {pyplumio-0.5.52 → pyplumio-0.5.54}/images/rs485.png +0 -0
  60. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/__init__.py +0 -0
  61. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/__main__.py +0 -0
  62. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/const.py +0 -0
  63. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/devices/ecoster.py +0 -0
  64. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/devices/mixer.py +0 -0
  65. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/devices/thermostat.py +0 -0
  66. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/exceptions.py +0 -0
  67. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/frames/messages.py +0 -0
  68. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/frames/requests.py +0 -0
  69. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/frames/responses.py +0 -0
  70. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/helpers/__init__.py +0 -0
  71. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/helpers/async_cache.py +0 -0
  72. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/helpers/event_manager.py +0 -0
  73. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/helpers/factory.py +0 -0
  74. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/helpers/task_manager.py +0 -0
  75. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/parameters/custom/__init__.py +0 -0
  76. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/parameters/custom/ecomax_860d3_hb.py +0 -0
  77. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/parameters/ecomax.py +0 -0
  78. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/parameters/mixer.py +0 -0
  79. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/parameters/thermostat.py +0 -0
  80. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/protocol.py +0 -0
  81. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/py.typed +0 -0
  82. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/__init__.py +0 -0
  83. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/alerts.py +0 -0
  84. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/boiler_load.py +0 -0
  85. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/boiler_power.py +0 -0
  86. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/ecomax_parameters.py +0 -0
  87. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/fan_power.py +0 -0
  88. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/frame_versions.py +0 -0
  89. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/fuel_consumption.py +0 -0
  90. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/fuel_level.py +0 -0
  91. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/lambda_sensor.py +0 -0
  92. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/mixer_parameters.py +0 -0
  93. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/mixer_sensors.py +0 -0
  94. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/modules.py +0 -0
  95. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/network_info.py +0 -0
  96. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/output_flags.py +0 -0
  97. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/outputs.py +0 -0
  98. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/pending_alerts.py +0 -0
  99. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/product_info.py +0 -0
  100. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/program_version.py +0 -0
  101. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/regulator_data.py +0 -0
  102. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/regulator_data_schema.py +0 -0
  103. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/schedules.py +0 -0
  104. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/statuses.py +0 -0
  105. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/temperatures.py +0 -0
  106. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/thermostat_parameters.py +0 -0
  107. {pyplumio-0.5.52 → pyplumio-0.5.54}/pyplumio/structures/thermostat_sensors.py +0 -0
  108. {pyplumio-0.5.52 → pyplumio-0.5.54}/requirements_docs.txt +0 -0
  109. {pyplumio-0.5.52 → pyplumio-0.5.54}/setup.cfg +0 -0
  110. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/__init__.py +0 -0
  111. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/conftest.py +0 -0
  112. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/devices/__init__.py +0 -0
  113. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/devices/test_ecoster.py +0 -0
  114. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/devices/test_init.py +0 -0
  115. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/devices/test_mixer.py +0 -0
  116. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/devices/test_thermostat.py +0 -0
  117. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/frames/test_init.py +0 -0
  118. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/frames/test_messages.py +0 -0
  119. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/frames/test_requests.py +0 -0
  120. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/frames/test_responses.py +0 -0
  121. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/helpers/__init__.py +0 -0
  122. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/helpers/test_async_cache.py +0 -0
  123. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/helpers/test_event_manager.py +0 -0
  124. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/helpers/test_factory.py +0 -0
  125. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/helpers/test_task_manager.py +0 -0
  126. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/helpers/test_uid.py +0 -0
  127. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/parameters/__init__.py +0 -0
  128. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/parameters/custom/__init__.py +0 -0
  129. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/parameters/custom/test_ecomax_860d3_hb.py +0 -0
  130. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/parameters/custom/test_init.py +0 -0
  131. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/parameters/test_ecomax.py +0 -0
  132. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/parameters/test_mixers.py +0 -0
  133. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/parameters/test_thermostats.py +0 -0
  134. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/ruff.toml +0 -0
  135. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/__init__.py +0 -0
  136. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_alerts.py +0 -0
  137. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_boiler_load.py +0 -0
  138. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_boiler_power.py +0 -0
  139. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_ecomax_parameters.py +0 -0
  140. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_fan_power.py +0 -0
  141. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_frame_versions.py +0 -0
  142. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_fuel_consumption.py +0 -0
  143. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_fuel_level.py +0 -0
  144. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_lambda_sensor.py +0 -0
  145. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_mixer_parameters.py +0 -0
  146. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_product_info.py +0 -0
  147. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/structures/test_schedules.py +0 -0
  148. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/test_init.py +0 -0
  149. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/test_main.py +0 -0
  150. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/test_protocol.py +0 -0
  151. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/messages/regulator_data.json +0 -0
  152. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/messages/sensor_data.json +0 -0
  153. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/parameters/ecomax_860d3_hb.json +0 -0
  154. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/alerts.json +0 -0
  155. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/ecomax_control.json +0 -0
  156. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/ecomax_parameters.json +0 -0
  157. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/mixer_parameters.json +0 -0
  158. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  159. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  160. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/set_schedule.json +0 -0
  161. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  162. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/requests/thermostat_parameters.json +0 -0
  163. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/alerts.json +0 -0
  164. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/device_available.json +0 -0
  165. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/ecomax_parameters.json +0 -0
  166. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/mixer_parameters.json +0 -0
  167. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/password.json +0 -0
  168. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/program_version.json +0 -0
  169. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/regulator_data_schema.json +0 -0
  170. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/schedules.json +0 -0
  171. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/thermostat_parameters.json +0 -0
  172. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/responses/uid.json +0 -0
  173. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  174. {pyplumio-0.5.52 → pyplumio-0.5.54}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -3,7 +3,7 @@
3
3
  # See https://pre-commit.com/hooks.html for more hooks
4
4
  repos:
5
5
  - repo: https://github.com/astral-sh/ruff-pre-commit
6
- rev: v0.11.11
6
+ rev: v0.12.11
7
7
  hooks:
8
8
  - id: ruff
9
9
  args:
@@ -13,6 +13,6 @@ repos:
13
13
  hooks:
14
14
  - id: codespell
15
15
  - repo: https://github.com/pre-commit/mirrors-mypy
16
- rev: v1.15.0
16
+ rev: v1.17.1
17
17
  hooks:
18
18
  - id: mypy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.5.52
3
+ Version: 0.5.54
4
4
  Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
5
5
  Author-email: Denis Paavilainen <denpa@denpa.pro>
6
6
  License: MIT License
@@ -25,26 +25,26 @@ Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: dataslots==1.2.0
27
27
  Requires-Dist: pyserial-asyncio==0.6
28
- Requires-Dist: typing-extensions==4.14.0
28
+ Requires-Dist: typing-extensions<5.0,>=4.14.0
29
29
  Provides-Extra: test
30
30
  Requires-Dist: codespell==2.4.1; extra == "test"
31
- Requires-Dist: coverage==7.9.1; extra == "test"
32
- Requires-Dist: freezegun==1.5.2; extra == "test"
33
- Requires-Dist: mypy==1.16.1; extra == "test"
31
+ Requires-Dist: coverage==7.10.5; extra == "test"
32
+ Requires-Dist: freezegun==1.5.5; extra == "test"
33
+ Requires-Dist: mypy==1.17.1; extra == "test"
34
34
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
35
35
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
36
36
  Requires-Dist: pytest==8.4.1; extra == "test"
37
- Requires-Dist: pytest-asyncio==1.0.0; extra == "test"
38
- Requires-Dist: ruff==0.11.13; extra == "test"
39
- Requires-Dist: tox==4.26.0; extra == "test"
40
- Requires-Dist: types-pyserial==3.5.0.20250326; extra == "test"
37
+ Requires-Dist: pytest-asyncio==1.1.0; extra == "test"
38
+ Requires-Dist: ruff==0.12.10; extra == "test"
39
+ Requires-Dist: tox==4.28.4; extra == "test"
40
+ Requires-Dist: types-pyserial==3.5.0.20250822; extra == "test"
41
41
  Provides-Extra: docs
42
42
  Requires-Dist: sphinx==8.1.3; extra == "docs"
43
43
  Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
44
44
  Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
45
45
  Provides-Extra: dev
46
46
  Requires-Dist: pyplumio[docs,test]; extra == "dev"
47
- Requires-Dist: pre-commit==4.2.0; extra == "dev"
47
+ Requires-Dist: pre-commit==4.3.0; extra == "dev"
48
48
  Requires-Dist: tomli==2.2.1; extra == "dev"
49
49
  Dynamic: license-file
50
50
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.5.52
3
+ Version: 0.5.54
4
4
  Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
5
5
  Author-email: Denis Paavilainen <denpa@denpa.pro>
6
6
  License: MIT License
@@ -25,26 +25,26 @@ Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: dataslots==1.2.0
27
27
  Requires-Dist: pyserial-asyncio==0.6
28
- Requires-Dist: typing-extensions==4.14.0
28
+ Requires-Dist: typing-extensions<5.0,>=4.14.0
29
29
  Provides-Extra: test
30
30
  Requires-Dist: codespell==2.4.1; extra == "test"
31
- Requires-Dist: coverage==7.9.1; extra == "test"
32
- Requires-Dist: freezegun==1.5.2; extra == "test"
33
- Requires-Dist: mypy==1.16.1; extra == "test"
31
+ Requires-Dist: coverage==7.10.5; extra == "test"
32
+ Requires-Dist: freezegun==1.5.5; extra == "test"
33
+ Requires-Dist: mypy==1.17.1; extra == "test"
34
34
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
35
35
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
36
36
  Requires-Dist: pytest==8.4.1; extra == "test"
37
- Requires-Dist: pytest-asyncio==1.0.0; extra == "test"
38
- Requires-Dist: ruff==0.11.13; extra == "test"
39
- Requires-Dist: tox==4.26.0; extra == "test"
40
- Requires-Dist: types-pyserial==3.5.0.20250326; extra == "test"
37
+ Requires-Dist: pytest-asyncio==1.1.0; extra == "test"
38
+ Requires-Dist: ruff==0.12.10; extra == "test"
39
+ Requires-Dist: tox==4.28.4; extra == "test"
40
+ Requires-Dist: types-pyserial==3.5.0.20250822; extra == "test"
41
41
  Provides-Extra: docs
42
42
  Requires-Dist: sphinx==8.1.3; extra == "docs"
43
43
  Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
44
44
  Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
45
45
  Provides-Extra: dev
46
46
  Requires-Dist: pyplumio[docs,test]; extra == "dev"
47
- Requires-Dist: pre-commit==4.2.0; extra == "dev"
47
+ Requires-Dist: pre-commit==4.3.0; extra == "dev"
48
48
  Requires-Dist: tomli==2.2.1; extra == "dev"
49
49
  Dynamic: license-file
50
50
 
@@ -63,7 +63,6 @@ pyplumio/helpers/async_cache.py
63
63
  pyplumio/helpers/event_manager.py
64
64
  pyplumio/helpers/factory.py
65
65
  pyplumio/helpers/task_manager.py
66
- pyplumio/helpers/timeout.py
67
66
  pyplumio/parameters/__init__.py
68
67
  pyplumio/parameters/ecomax.py
69
68
  pyplumio/parameters/mixer.py
@@ -122,7 +121,6 @@ tests/helpers/test_async_cache.py
122
121
  tests/helpers/test_event_manager.py
123
122
  tests/helpers/test_factory.py
124
123
  tests/helpers/test_task_manager.py
125
- tests/helpers/test_timeout.py
126
124
  tests/helpers/test_uid.py
127
125
  tests/parameters/__init__.py
128
126
  tests/parameters/test_ecomax.py
@@ -1,10 +1,10 @@
1
1
  dataslots==1.2.0
2
2
  pyserial-asyncio==0.6
3
- typing-extensions==4.14.0
3
+ typing-extensions<5.0,>=4.14.0
4
4
 
5
5
  [dev]
6
6
  pyplumio[docs,test]
7
- pre-commit==4.2.0
7
+ pre-commit==4.3.0
8
8
  tomli==2.2.1
9
9
 
10
10
  [docs]
@@ -14,13 +14,13 @@ readthedocs-sphinx-search==0.3.2
14
14
 
15
15
  [test]
16
16
  codespell==2.4.1
17
- coverage==7.9.1
18
- freezegun==1.5.2
19
- mypy==1.16.1
17
+ coverage==7.10.5
18
+ freezegun==1.5.5
19
+ mypy==1.17.1
20
20
  numpy<3.0.0,>=2.0.0
21
21
  pyserial-asyncio-fast==0.16
22
22
  pytest==8.4.1
23
- pytest-asyncio==1.0.0
24
- ruff==0.11.13
25
- tox==4.26.0
26
- types-pyserial==3.5.0.20250326
23
+ pytest-asyncio==1.1.0
24
+ ruff==0.12.10
25
+ tox==4.28.4
26
+ types-pyserial==3.5.0.20250822
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '0.5.52'
21
- __version_tuple__ = version_tuple = (0, 5, 52)
31
+ __version__ = version = '0.5.54'
32
+ __version_tuple__ = version_tuple = (0, 5, 54)
33
+
34
+ __commit_id__ = commit_id = 'g7b7ef864f'
@@ -11,8 +11,8 @@ from serial import EIGHTBITS, PARITY_NONE, STOPBITS_ONE
11
11
 
12
12
  from pyplumio.exceptions import ConnectionFailedError
13
13
  from pyplumio.helpers.task_manager import TaskManager
14
- from pyplumio.helpers.timeout import timeout
15
14
  from pyplumio.protocol import AsyncProtocol, Protocol
15
+ from pyplumio.utils import timeout
16
16
 
17
17
  _LOGGER = logging.getLogger(__name__)
18
18
 
@@ -33,6 +33,13 @@ class DataType(ABC, Generic[T]):
33
33
 
34
34
  return f"{self.__class__.__name__}()"
35
35
 
36
+ def __hash__(self) -> int:
37
+ """Return a hash of the data type based on its value."""
38
+ if hasattr(self, "_value"):
39
+ return hash(self._value)
40
+
41
+ return hash(type(self))
42
+
36
43
  def __eq__(self, other: object) -> bool:
37
44
  """Compare if this data type is equal to other."""
38
45
  if (
@@ -195,6 +195,7 @@ class PhysicalDevice(Device, ABC):
195
195
  """
196
196
  _LOGGER.info("Requesting '%s' with %s", name, repr(frame_type))
197
197
  request = await Request.create(frame_type, recipient=self.address)
198
+ initial_retries = retries
198
199
  while retries > 0:
199
200
  try:
200
201
  self.queue.put_nowait(request)
@@ -204,7 +205,7 @@ class PhysicalDevice(Device, ABC):
204
205
 
205
206
  raise RequestError(
206
207
  f"Failed to request '{name}' with frame type '{frame_type}' after "
207
- f"{retries} retries.",
208
+ f"{initial_retries} retries.",
208
209
  frame_type=frame_type,
209
210
  )
210
211
 
@@ -236,7 +236,7 @@ class EcoMAX(PhysicalDevice):
236
236
  async def on_event_setup(self, setup: bool) -> None:
237
237
  """Request frames required to set up an ecoMAX entry."""
238
238
  _LOGGER.debug("Setting up device entry")
239
- await self.wait_for(ATTR_SENSORS)
239
+ await self.wait_for(ATTR_SENSORS, timeout=30.0)
240
240
  results = await asyncio.gather(
241
241
  *(
242
242
  self.request(description.provides, description.frame_type)
@@ -56,6 +56,9 @@ class SupportsComparison(Protocol):
56
56
 
57
57
  __slots__ = ()
58
58
 
59
+ def __hash__(self) -> int:
60
+ """Return a hash of the value."""
61
+
59
62
  def __eq__(self: SupportsComparison, other: SupportsComparison) -> bool:
60
63
  """Compare a value."""
61
64
 
@@ -130,6 +133,10 @@ class Filter(ABC):
130
133
  self._callback = callback
131
134
  self._value = UNDEFINED
132
135
 
136
+ def __hash__(self) -> int:
137
+ """Return a hash of the filter based on its callback."""
138
+ return hash(self._callback)
139
+
133
140
  def __eq__(self, other: Any) -> bool:
134
141
  """Compare callbacks."""
135
142
  if isinstance(other, Filter):
@@ -179,9 +186,10 @@ class _Aggregate(Filter):
179
186
  self._values.append(new_value)
180
187
  time_since_call = current_time - self._last_call_time
181
188
  if time_since_call >= self._timeout or len(self._values) >= self._sample_size:
182
- result = await self._callback(
189
+ sum_of_values = (
183
190
  np.sum(self._values) if numpy_installed else sum(self._values)
184
191
  )
192
+ result = await self._callback(float(sum_of_values))
185
193
  self._last_call_time = current_time
186
194
  self._values = []
187
195
  return result
@@ -114,6 +114,19 @@ class Frame(ABC):
114
114
  self._data = data if not kwargs else ensure_dict(data, kwargs)
115
115
  self._message = message
116
116
 
117
+ def __hash__(self) -> int:
118
+ """Return a hash of the frame based on its values."""
119
+ return hash(
120
+ (
121
+ self.recipient,
122
+ self.sender,
123
+ self.econet_type,
124
+ self.econet_version,
125
+ self._message,
126
+ frozenset(self._data.items()) if self._data else None,
127
+ )
128
+ )
129
+
117
130
  def __eq__(self, other: object) -> bool:
118
131
  """Compare if this frame is equal to other."""
119
132
  if isinstance(other, Frame):
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from abc import ABC, abstractmethod
6
6
  import asyncio
7
7
  from contextlib import suppress
8
- from dataclasses import dataclass
8
+ from dataclasses import asdict, dataclass
9
9
  import logging
10
10
  from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, get_args
11
11
 
@@ -115,6 +115,10 @@ class Parameter(ABC):
115
115
  f"index={self._index})"
116
116
  )
117
117
 
118
+ def __hash__(self) -> int:
119
+ """Return a hash of the parameter based on its values."""
120
+ return hash(frozenset(asdict(self.values).items()))
121
+
118
122
  def _call_relational_method(self, method_to_call: str, other: Any) -> Any:
119
123
  """Call a specified relational method."""
120
124
  if isinstance(other, Parameter):
@@ -20,7 +20,7 @@ from pyplumio.frames import (
20
20
  bcc,
21
21
  struct_header,
22
22
  )
23
- from pyplumio.helpers.timeout import timeout
23
+ from pyplumio.utils import timeout
24
24
 
25
25
  READER_TIMEOUT: Final = 10
26
26
  WRITER_TIMEOUT: Final = 10
@@ -67,7 +67,7 @@ class FrameWriter:
67
67
  await self._writer.wait_closed()
68
68
 
69
69
 
70
- class BufferManager:
70
+ class BufferedReader:
71
71
  """Represents a buffered reader for reading frames."""
72
72
 
73
73
  __slots__ = ("_buffer", "_reader")
@@ -89,7 +89,7 @@ class BufferManager:
89
89
  try:
90
90
  data = await self._reader.readexactly(bytes_to_read)
91
91
  self._buffer.extend(data)
92
- self.trim_to(size)
92
+ self.trim_to(DEFAULT_BUFFER_SIZE)
93
93
  except IncompleteReadError as e:
94
94
  raise ReadError(
95
95
  f"Incomplete read. Tried to read {bytes_to_read} additional bytes "
@@ -104,22 +104,36 @@ class BufferManager:
104
104
  f"Serial connection broken while trying to ensure {size} bytes: {e}"
105
105
  ) from e
106
106
 
107
+ async def peek(self, size: int) -> bytes:
108
+ """Read the specified number of bytes without consuming them."""
109
+ await self.ensure_buffer(size)
110
+ return memoryview(self._buffer)[:size].tobytes()
111
+
107
112
  async def consume(self, size: int) -> None:
108
113
  """Consume the specified number of bytes from the buffer."""
109
114
  await self.ensure_buffer(size)
110
115
  self._buffer = self._buffer[size:]
111
116
 
112
- async def peek(self, size: int) -> bytearray:
113
- """Read the specified number of bytes without consuming them."""
114
- await self.ensure_buffer(size)
115
- return self._buffer[:size]
116
-
117
- async def read(self, size: int) -> bytearray:
118
- """Read the bytes from buffer or stream and consume them."""
117
+ async def read_into_buffer(self, size: int) -> None:
118
+ """Read the specified number of bytes from the stream."""
119
119
  try:
120
- return await self.peek(size)
121
- finally:
122
- await self.consume(size)
120
+ chunk = await self._reader.read(size)
121
+ except asyncio.CancelledError:
122
+ _LOGGER.debug("Read operation cancelled while filling internal buffer.")
123
+ raise
124
+ except Exception as e:
125
+ raise OSError(
126
+ f"Serial connection broken while filling internal buffer: {e}"
127
+ ) from e
128
+
129
+ if not chunk:
130
+ _LOGGER.debug("Stream ended while filling internal buffer.")
131
+ raise OSError(
132
+ "Serial connection broken: stream ended while filling internal buffer"
133
+ )
134
+
135
+ self._buffer.extend(chunk)
136
+ self.trim_to(DEFAULT_BUFFER_SIZE)
123
137
 
124
138
  def seek_to(self, delimiter: SupportsIndex) -> bool:
125
139
  """Trim the buffer to the first occurrence of the delimiter.
@@ -137,27 +151,6 @@ class BufferManager:
137
151
  if len(self._buffer) > size:
138
152
  self._buffer = self._buffer[-size:]
139
153
 
140
- async def fill(self) -> None:
141
- """Fill the buffer with data from the stream."""
142
- try:
143
- chunk = await self._reader.read(MAX_FRAME_LENGTH)
144
- except asyncio.CancelledError:
145
- _LOGGER.debug("Read operation cancelled while filling read buffer.")
146
- raise
147
- except Exception as e:
148
- raise OSError(
149
- f"Serial connection broken while filling read buffer: {e}"
150
- ) from e
151
-
152
- if not chunk:
153
- _LOGGER.debug("Stream ended while filling read buffer.")
154
- raise OSError(
155
- "Serial connection broken: stream ended while filling read buffer"
156
- )
157
-
158
- self._buffer.extend(chunk)
159
- self.trim_to(DEFAULT_BUFFER_SIZE)
160
-
161
154
  @property
162
155
  def buffer(self) -> bytearray:
163
156
  """Return the internal buffer."""
@@ -177,22 +170,23 @@ class Header(NamedTuple):
177
170
  class FrameReader:
178
171
  """Represents a frame reader."""
179
172
 
180
- __slots__ = ("_buffer",)
173
+ __slots__ = ("_reader",)
181
174
 
182
- _buffer: BufferManager
175
+ _reader: BufferedReader
183
176
 
184
177
  def __init__(self, reader: StreamReader) -> None:
185
178
  """Initialize a new frame reader."""
186
- self._buffer = BufferManager(reader)
179
+ self._reader = BufferedReader(reader)
187
180
 
188
181
  async def _read_header(self) -> Header:
189
182
  """Locate and read a frame header."""
190
183
  while True:
191
- if self._buffer.seek_to(FRAME_START):
192
- header_bytes = await self._buffer.peek(HEADER_SIZE)
193
- return Header(*struct_header.unpack_from(header_bytes)[DELIMITER_SIZE:])
184
+ if self._reader.seek_to(FRAME_START):
185
+ header_bytes = await self._reader.peek(HEADER_SIZE)
186
+ header_fields = struct_header.unpack_from(header_bytes)
187
+ return Header(*header_fields[DELIMITER_SIZE:])
194
188
 
195
- await self._buffer.fill()
189
+ await self._reader.read_into_buffer(MAX_FRAME_LENGTH)
196
190
 
197
191
  @timeout(READER_TIMEOUT)
198
192
  async def read(self) -> Frame | None:
@@ -201,22 +195,22 @@ class FrameReader:
201
195
  frame_length, recipient, sender, econet_type, econet_version = header
202
196
 
203
197
  if frame_length > MAX_FRAME_LENGTH or frame_length < MIN_FRAME_LENGTH:
204
- await self._buffer.consume(HEADER_SIZE)
198
+ await self._reader.consume(HEADER_SIZE)
205
199
  raise ReadError(
206
200
  f"Unexpected frame length ({frame_length}), expected between "
207
201
  f"{MIN_FRAME_LENGTH} and {MAX_FRAME_LENGTH}"
208
202
  )
209
203
 
210
- frame_bytes = await self._buffer.peek(frame_length)
204
+ frame_bytes = await self._reader.peek(frame_length)
211
205
  checksum = bcc(frame_bytes[:BCC_INDEX])
212
206
  if checksum != frame_bytes[BCC_INDEX]:
213
- await self._buffer.consume(HEADER_SIZE)
207
+ await self._reader.consume(HEADER_SIZE)
214
208
  raise ChecksumError(
215
209
  f"Incorrect frame checksum: calculated {checksum}, "
216
210
  f"expected {frame_bytes[BCC_INDEX]}. Frame data: {frame_bytes.hex()}"
217
211
  )
218
212
 
219
- await self._buffer.consume(frame_length)
213
+ await self._reader.consume(frame_length)
220
214
  if recipient not in (DeviceType.ECONET, DeviceType.ALL):
221
215
  _LOGGER.debug(
222
216
  "Skipping frame intended for different recipient (%s)", recipient
@@ -2,9 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Mapping
5
+ import asyncio
6
+ from collections.abc import Awaitable, Callable, Mapping
7
+ from functools import wraps
6
8
  from typing import TypeVar
7
9
 
10
+ from typing_extensions import ParamSpec
11
+
8
12
  KT = TypeVar("KT") # Key type.
9
13
  VT = TypeVar("VT") # Value type.
10
14
 
@@ -40,4 +44,24 @@ def is_divisible(a: float, b: float, precision: int = 6) -> bool:
40
44
  return a_scaled % b_scaled == 0
41
45
 
42
46
 
43
- __all__ = ["ensure_dict", "is_divisible", "to_camelcase"]
47
+ T = TypeVar("T")
48
+ P = ParamSpec("P")
49
+
50
+
51
+ def timeout(
52
+ seconds: float,
53
+ ) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]:
54
+ """Decorate a timeout for the awaitable."""
55
+
56
+ def decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
57
+ @wraps(func)
58
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
59
+ return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
60
+
61
+ setattr(wrapper, "_has_timeout_seconds", seconds)
62
+ return wrapper
63
+
64
+ return decorator
65
+
66
+
67
+ __all__ = ["ensure_dict", "is_divisible", "to_camelcase", "timeout"]
@@ -26,7 +26,7 @@ classifiers = [
26
26
  dependencies = [
27
27
  "dataslots==1.2.0",
28
28
  "pyserial-asyncio==0.6",
29
- "typing-extensions==4.14.0"
29
+ "typing-extensions>=4.14.0,<5.0"
30
30
  ]
31
31
  dynamic = ["version"]
32
32
 
@@ -38,16 +38,16 @@ dynamic = ["version"]
38
38
  [project.optional-dependencies]
39
39
  test = [
40
40
  "codespell==2.4.1",
41
- "coverage==7.9.1",
42
- "freezegun==1.5.2",
43
- "mypy==1.16.1",
41
+ "coverage==7.10.5",
42
+ "freezegun==1.5.5",
43
+ "mypy==1.17.1",
44
44
  "numpy<3.0.0,>=2.0.0",
45
45
  "pyserial-asyncio-fast==0.16",
46
46
  "pytest==8.4.1",
47
- "pytest-asyncio==1.0.0",
48
- "ruff==0.11.13",
49
- "tox==4.26.0",
50
- "types-pyserial==3.5.0.20250326"
47
+ "pytest-asyncio==1.1.0",
48
+ "ruff==0.12.10",
49
+ "tox==4.28.4",
50
+ "types-pyserial==3.5.0.20250822"
51
51
  ]
52
52
  docs = [
53
53
  "sphinx==8.1.3",
@@ -56,7 +56,7 @@ docs = [
56
56
  ]
57
57
  dev = [
58
58
  "pyplumio[test,docs]",
59
- "pre-commit==4.2.0",
59
+ "pre-commit==4.3.0",
60
60
  "tomli==2.2.1"
61
61
  ]
62
62
 
@@ -1,3 +1,3 @@
1
1
  dataslots==1.2.0
2
2
  pyserial-asyncio==0.6
3
- typing-extensions==4.14.0
3
+ typing-extensions>=4.14.0,<5.0
@@ -0,0 +1,13 @@
1
+ codespell==2.4.1
2
+ coverage==7.10.5
3
+ freezegun==1.5.5
4
+ mypy==1.17.1
5
+ numpy<3.0.0,>=2.0.0
6
+ pre-commit==4.3.0
7
+ pyserial-asyncio-fast==0.16
8
+ pytest-asyncio==1.1.0
9
+ pytest==8.4.1
10
+ ruff==0.12.10
11
+ tomli==2.2.1
12
+ tox==4.28.4
13
+ types-pyserial==3.5.0.20250822
@@ -206,7 +206,7 @@ async def test_ecomax_setup(mock_wait_for, mock_request, ecomax: EcoMAX) -> None
206
206
  await ecomax.wait_until_done()
207
207
  assert ATTR_FRAME_ERRORS not in ecomax.data
208
208
  assert mock_request.await_count == len(REQUIRED)
209
- mock_wait_for.assert_awaited_once_with(ATTR_SENSORS)
209
+ mock_wait_for.assert_awaited_once_with(ATTR_SENSORS, timeout=30.0)
210
210
 
211
211
 
212
212
  @patch(
@@ -220,7 +220,7 @@ async def test_ecomax_setup_errors(mock_wait_for, mock_request, ecomax: EcoMAX)
220
220
  await ecomax.wait_until_done()
221
221
  assert FrameType.REQUEST_ALERTS in ecomax.get_nowait(ATTR_FRAME_ERRORS, [])
222
222
  assert mock_request.await_count == len(REQUIRED)
223
- mock_wait_for.assert_awaited_once_with(ATTR_SENSORS)
223
+ mock_wait_for.assert_awaited_once_with(ATTR_SENSORS, timeout=30.0)
224
224
 
225
225
 
226
226
  @patch("asyncio.Queue.put")
@@ -139,6 +139,15 @@ class TestParameter:
139
139
  assert result == 6
140
140
  assert isinstance(result, int)
141
141
 
142
+ def test_hash(self, parameter: Parameter) -> None:
143
+ """Test __hash__.
144
+
145
+ Checks parameter hashing.
146
+ """
147
+ assert hash(parameter) == hash(
148
+ frozenset({("value", 6), ("min_value", 0), ("max_value", 10)})
149
+ )
150
+
142
151
  @pytest.mark.parametrize(
143
152
  ("func_name", "other", "expected_result", "expected_type"),
144
153
  [
@@ -6,22 +6,21 @@ from asyncio import StreamReader, StreamWriter
6
6
  from importlib import reload
7
7
  import logging
8
8
  import sys
9
- from typing import Final
9
+ from typing import Any, Final
10
10
  from unittest.mock import AsyncMock, Mock, patch
11
11
 
12
12
  import pytest
13
13
  from serial import EIGHTBITS, PARITY_NONE, STOPBITS_ONE
14
14
 
15
+ import pyplumio.connection
15
16
  from pyplumio.connection import Connection, SerialConnection, TcpConnection
16
17
  from pyplumio.exceptions import ConnectionFailedError
17
18
  from pyplumio.protocol import Protocol
18
19
 
19
20
 
20
21
  @pytest.fixture(name="use_fast", params=(True, False))
21
- def fixture_use_fast(request, monkeypatch, caplog):
22
+ def fixture_use_fast(request, monkeypatch, caplog) -> Any:
22
23
  """Try with and without serial-asyncio-fast package."""
23
- import pyplumio.connection
24
-
25
24
  if not request.param:
26
25
  monkeypatch.setitem(sys.modules, "serial_asyncio_fast", None)
27
26