python-rako-2025 0.2.0__tar.gz → 0.3.7__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 (25) hide show
  1. {python_rako_2025-0.2.0/python_rako_2025.egg-info → python_rako_2025-0.3.7}/PKG-INFO +1 -8
  2. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/pyproject.toml +4 -1
  3. python_rako_2025-0.3.7/python_rako/__version__.py +1 -0
  4. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako/bridge.py +32 -8
  5. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7/python_rako_2025.egg-info}/PKG-INFO +1 -8
  6. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako_2025.egg-info/SOURCES.txt +0 -1
  7. python_rako_2025-0.3.7/setup.py +3 -0
  8. python_rako_2025-0.2.0/python_rako/__version__.py +0 -3
  9. python_rako_2025-0.2.0/python_rako_2025.egg-info/not-zip-safe +0 -1
  10. python_rako_2025-0.2.0/setup.py +0 -55
  11. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/LICENSE +0 -0
  12. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/README.md +0 -0
  13. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako/__init__.py +0 -0
  14. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako/const.py +0 -0
  15. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako/exceptions.py +0 -0
  16. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako/helpers.py +0 -0
  17. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako/model.py +0 -0
  18. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako/py.typed +0 -0
  19. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako_2025.egg-info/dependency_links.txt +0 -0
  20. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako_2025.egg-info/requires.txt +0 -0
  21. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/python_rako_2025.egg-info/top_level.txt +0 -0
  22. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/setup.cfg +0 -0
  23. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/tests/test_bridge.py +0 -0
  24. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/tests/test_helpers.py +0 -0
  25. {python_rako_2025-0.2.0 → python_rako_2025-0.3.7}/tests/test_model.py +0 -0
@@ -1,15 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-rako-2025
3
- Version: 0.2.0
3
+ Version: 0.3.7
4
4
  Summary: Asynchronous Python client for Rako Controls Lighting
5
- Home-page: https://github.com/simonleigh/python-rako
6
- Author: Simon Leigh
7
5
  Author-email: Simon Leigh <simonleigh@users.noreply.github.com>
8
6
  Project-URL: Homepage, https://github.com/simonleigh/python-rako
9
7
  Project-URL: Repository, https://github.com/simonleigh/python-rako
10
8
  Project-URL: Issues, https://github.com/simonleigh/python-rako/issues
11
9
  Keywords: rako,controls,api,async,client
12
- Platform: any
13
10
  Classifier: Development Status :: 4 - Beta
14
11
  Classifier: Framework :: AsyncIO
15
12
  Classifier: Intended Audience :: Developers
@@ -28,11 +25,7 @@ License-File: LICENSE
28
25
  Requires-Dist: aiohttp>=3.10.0
29
26
  Requires-Dist: asyncio-dgram>=2.2.0
30
27
  Requires-Dist: xmltodict>=0.13.0
31
- Dynamic: author
32
- Dynamic: home-page
33
28
  Dynamic: license-file
34
- Dynamic: platform
35
- Dynamic: requires-python
36
29
 
37
30
  # Python: Rako Controls API Client
38
31
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-rako-2025"
7
- version = "0.2.0"
7
+ dynamic = ["version"]
8
8
  description = "Asynchronous Python client for Rako Controls Lighting"
9
9
  readme = "README.md"
10
10
  keywords = ["rako", "controls", "api", "async", "client"]
@@ -44,6 +44,9 @@ include = ["python_rako*"]
44
44
  [tool.setuptools.package-data]
45
45
  python_rako = ["py.typed"]
46
46
 
47
+ [tool.setuptools.dynamic]
48
+ version = {file = "python_rako/__version__.py"}
49
+
47
50
  [tool.ruff]
48
51
  target-version = "py310"
49
52
  line-length = 100
@@ -0,0 +1 @@
1
+ 0.3.7
@@ -160,16 +160,38 @@ class Bridge:
160
160
  rako_xml: str = await response.text()
161
161
  return rako_xml
162
162
 
163
+ async def discover_devices(
164
+ self, session: aiohttp.ClientSession
165
+ ) -> tuple[list[Light], list[Ventilation]]:
166
+ """Discover all devices by fetching XML once and parsing all device types.
167
+
168
+ Returns a tuple of (lights, ventilation) to avoid race conditions.
169
+ """
170
+ rako_xml = await self.get_rako_xml(session)
171
+
172
+ lights: list[Light] = []
173
+ ventilation: list[Ventilation] = []
174
+
175
+ for device in self.get_devices_from_discovery_xml(rako_xml):
176
+ if isinstance(device, (RoomLight, ChannelLight)):
177
+ lights.append(device) # type: ignore[arg-type]
178
+ elif isinstance(device, (RoomVentilation, ChannelVentilation)):
179
+ ventilation.append(device) # type: ignore[arg-type]
180
+
181
+ return lights, ventilation
182
+
163
183
  async def discover_lights(
164
184
  self, session: aiohttp.ClientSession
165
- ) -> AsyncGenerator[Light]:
185
+ ) -> AsyncGenerator[RoomLight | ChannelLight, None]:
186
+ """Discover lights by fetching XML once and filtering for lights."""
166
187
  rako_xml = await self.get_rako_xml(session)
167
188
  for light in self.get_lights_from_discovery_xml(rako_xml):
168
189
  yield light
169
190
 
170
191
  async def discover_ventilation(
171
192
  self, session: aiohttp.ClientSession
172
- ) -> AsyncGenerator[Ventilation]:
193
+ ) -> AsyncGenerator[RoomVentilation | ChannelVentilation, None]:
194
+ """Discover ventilation by fetching XML once and filtering for ventilation."""
173
195
  rako_xml = await self.get_rako_xml(session)
174
196
  for device in self.get_devices_from_discovery_xml(rako_xml, "Ventilation"):
175
197
  if isinstance(device, (RoomVentilation, ChannelVentilation)):
@@ -177,7 +199,8 @@ class Bridge:
177
199
 
178
200
  async def discover_all_devices(
179
201
  self, session: aiohttp.ClientSession
180
- ) -> AsyncGenerator[Light | Ventilation]:
202
+ ) -> AsyncGenerator[RoomLight | ChannelLight | RoomVentilation | ChannelVentilation, None]:
203
+ """Discover all devices by fetching XML once."""
181
204
  rako_xml = await self.get_rako_xml(session)
182
205
  for device in self.get_devices_from_discovery_xml(rako_xml):
183
206
  yield device
@@ -211,20 +234,20 @@ class Bridge:
211
234
  )
212
235
 
213
236
  @staticmethod
214
- def get_lights_from_discovery_xml(xml: str) -> Generator[Light]:
237
+ def get_lights_from_discovery_xml(xml: str) -> Generator[RoomLight | ChannelLight, None, None]:
215
238
  for device in Bridge.get_devices_from_discovery_xml(xml, "Lights"):
216
239
  if isinstance(device, (RoomLight, ChannelLight)):
217
240
  yield device
218
241
 
219
242
  @staticmethod
220
- def get_all_devices_from_discovery_xml(xml: str) -> Generator[Light | Ventilation]:
243
+ def get_all_devices_from_discovery_xml(xml: str) -> Generator[RoomLight | ChannelLight | RoomVentilation | ChannelVentilation, None, None]:
221
244
  """Get all devices (lights and ventilation) from discovery XML."""
222
245
  return Bridge.get_devices_from_discovery_xml(xml)
223
246
 
224
247
  @staticmethod
225
248
  def get_devices_from_discovery_xml(
226
249
  xml: str, device_types: str | list[str] | None = None
227
- ) -> Generator[Light | Ventilation]:
250
+ ) -> Generator[RoomLight | ChannelLight | RoomVentilation | ChannelVentilation, None, None]:
228
251
  # Handle different input types for backward compatibility
229
252
  if device_types is None or device_types == "All":
230
253
  target_types = {"Lights", "Ventilation"}
@@ -284,11 +307,12 @@ class Bridge:
284
307
  if not resp:
285
308
  return None
286
309
 
287
- data, (remote_ip, _) = resp
310
+ data, addr = resp
311
+ remote_ip, _ = addr
288
312
  if remote_ip != self.host:
289
313
  return None
290
314
 
291
- byte_list = list(data)
315
+ byte_list = list(data) if isinstance(data, (bytes, bytearray)) else [int(data)]
292
316
  _LOGGER.debug("Received bytes: %s", byte_list)
293
317
  message = deserialise_byte_list(byte_list)
294
318
  _LOGGER.debug("Deserialised received message as: %s", message)
@@ -1,15 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-rako-2025
3
- Version: 0.2.0
3
+ Version: 0.3.7
4
4
  Summary: Asynchronous Python client for Rako Controls Lighting
5
- Home-page: https://github.com/simonleigh/python-rako
6
- Author: Simon Leigh
7
5
  Author-email: Simon Leigh <simonleigh@users.noreply.github.com>
8
6
  Project-URL: Homepage, https://github.com/simonleigh/python-rako
9
7
  Project-URL: Repository, https://github.com/simonleigh/python-rako
10
8
  Project-URL: Issues, https://github.com/simonleigh/python-rako/issues
11
9
  Keywords: rako,controls,api,async,client
12
- Platform: any
13
10
  Classifier: Development Status :: 4 - Beta
14
11
  Classifier: Framework :: AsyncIO
15
12
  Classifier: Intended Audience :: Developers
@@ -28,11 +25,7 @@ License-File: LICENSE
28
25
  Requires-Dist: aiohttp>=3.10.0
29
26
  Requires-Dist: asyncio-dgram>=2.2.0
30
27
  Requires-Dist: xmltodict>=0.13.0
31
- Dynamic: author
32
- Dynamic: home-page
33
28
  Dynamic: license-file
34
- Dynamic: platform
35
- Dynamic: requires-python
36
29
 
37
30
  # Python: Rako Controls API Client
38
31
 
@@ -13,7 +13,6 @@ python_rako/py.typed
13
13
  python_rako_2025.egg-info/PKG-INFO
14
14
  python_rako_2025.egg-info/SOURCES.txt
15
15
  python_rako_2025.egg-info/dependency_links.txt
16
- python_rako_2025.egg-info/not-zip-safe
17
16
  python_rako_2025.egg-info/requires.txt
18
17
  python_rako_2025.egg-info/top_level.txt
19
18
  tests/test_bridge.py
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+
3
+ setup()
@@ -1,3 +0,0 @@
1
- """AUTOGENERATED ON RELEASE."""
2
-
3
- __version__ = "0.2.0"
@@ -1,55 +0,0 @@
1
- import os
2
-
3
- from setuptools import find_packages, setup
4
-
5
-
6
- def read(*parts):
7
- """Read file."""
8
- filename = os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts)
9
- with open(filename, encoding="utf-8") as fp:
10
- return fp.read()
11
-
12
-
13
- with open("README.md") as readme_file:
14
- readme = readme_file.read()
15
-
16
- setup(
17
- name="python-rako-2025",
18
- version="0.2.0",
19
- license="MIT",
20
- url="https://github.com/simonleigh/python-rako",
21
- author="Simon Leigh",
22
- author_email="simonleigh@users.noreply.github.com",
23
- description="Asynchronous Python client for Rako Controls Lighting.",
24
- keywords=["rako", "controls", "api", "async", "client"],
25
- long_description=readme,
26
- long_description_content_type="text/markdown",
27
- packages=find_packages(include=["python_rako"]),
28
- zip_safe=False,
29
- include_package_data=True,
30
- platforms="any",
31
- package_data={
32
- "python_rako": ["py.typed"],
33
- },
34
- install_requires=[
35
- "aiohttp>=3.10.0",
36
- "asyncio-dgram>=2.2.0",
37
- "xmltodict>=0.13.0",
38
- ],
39
- classifiers=[
40
- "Intended Audience :: Developers",
41
- "License :: OSI Approved :: MIT License",
42
- "Operating System :: OS Independent",
43
- "Programming Language :: Python",
44
- "Programming Language :: Python :: 3",
45
- "Programming Language :: Python :: 3.10",
46
- "Programming Language :: Python :: 3.11",
47
- "Programming Language :: Python :: 3.12",
48
- "Programming Language :: Python :: 3.13",
49
- "Natural Language :: English",
50
- "Topic :: Software Development :: Libraries :: Python Modules",
51
- "Framework :: AsyncIO",
52
- "Development Status :: 4 - Beta",
53
- ],
54
- python_requires=">=3.10",
55
- )