lifx-async 4.3.7__tar.gz → 4.3.9__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 (146) hide show
  1. {lifx_async-4.3.7 → lifx_async-4.3.9}/.github/workflows/ci.yml +2 -0
  2. {lifx_async-4.3.7 → lifx_async-4.3.9}/CLAUDE.md +14 -26
  3. {lifx_async-4.3.7 → lifx_async-4.3.9}/PKG-INFO +1 -1
  4. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/changelog.md +16 -0
  5. {lifx_async-4.3.7 → lifx_async-4.3.9}/pyproject.toml +5 -3
  6. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/devices/base.py +0 -2
  7. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/network/connection.py +9 -8
  8. lifx_async-4.3.9/tests/conftest.py +530 -0
  9. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_api/test_api_apply_theme.py +0 -3
  10. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_api/test_api_batch_errors.py +100 -77
  11. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_api/test_api_batch_operations.py +3 -0
  12. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_api/test_api_discovery.py +60 -41
  13. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_api/test_api_organization.py +5 -0
  14. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_color.py +4 -0
  15. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/test_base.py +26 -18
  16. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_effects/test_colorloop.py +187 -0
  17. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_network/test_concurrent_requests.py +49 -30
  18. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_network/test_connection.py +8 -8
  19. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_network/test_discovery_devices.py +12 -16
  20. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_network/test_discovery_errors.py +6 -4
  21. lifx_async-4.3.9/tests/test_network/test_transport.py +382 -0
  22. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_protocol/test_serializer.py +224 -0
  23. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_theme/test_apply_theme.py +3 -0
  24. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_theme/test_canvas.py +64 -0
  25. {lifx_async-4.3.7 → lifx_async-4.3.9}/uv.lock +11 -205
  26. lifx_async-4.3.7/tests/conftest.py +0 -470
  27. lifx_async-4.3.7/tests/test_network/test_transport.py +0 -67
  28. {lifx_async-4.3.7 → lifx_async-4.3.9}/.claude/settings.json +0 -0
  29. {lifx_async-4.3.7 → lifx_async-4.3.9}/.github/dependabot.yml +0 -0
  30. {lifx_async-4.3.7 → lifx_async-4.3.9}/.github/labeler.yml +0 -0
  31. {lifx_async-4.3.7 → lifx_async-4.3.9}/.github/workflows/docs.yml +0 -0
  32. {lifx_async-4.3.7 → lifx_async-4.3.9}/.github/workflows/pr-automation.yml +0 -0
  33. {lifx_async-4.3.7 → lifx_async-4.3.9}/.gitignore +0 -0
  34. {lifx_async-4.3.7 → lifx_async-4.3.9}/.pre-commit-config.yaml +0 -0
  35. {lifx_async-4.3.7 → lifx_async-4.3.9}/LICENSE +0 -0
  36. {lifx_async-4.3.7 → lifx_async-4.3.9}/README.md +0 -0
  37. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/colors.md +0 -0
  38. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/devices.md +0 -0
  39. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/effects.md +0 -0
  40. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/exceptions.md +0 -0
  41. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/high-level.md +0 -0
  42. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/index.md +0 -0
  43. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/network.md +0 -0
  44. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/protocol.md +0 -0
  45. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/api/themes.md +0 -0
  46. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/architecture/effects-architecture.md +0 -0
  47. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/architecture/overview.md +0 -0
  48. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/faq.md +0 -0
  49. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/getting-started/effects.md +0 -0
  50. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/getting-started/installation.md +0 -0
  51. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/getting-started/quickstart.md +0 -0
  52. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/getting-started/themes.md +0 -0
  53. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/index.md +0 -0
  54. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/migration/effect-api-changes.md +0 -0
  55. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/stylesheets/extra.css +0 -0
  56. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/user-guide/advanced-usage.md +0 -0
  57. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/user-guide/effects-custom.md +0 -0
  58. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/user-guide/effects-troubleshooting.md +0 -0
  59. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/user-guide/protocol-deep-dive.md +0 -0
  60. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/user-guide/themes.md +0 -0
  61. {lifx_async-4.3.7 → lifx_async-4.3.9}/docs/user-guide/troubleshooting.md +0 -0
  62. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/01_simple_discovery.py +0 -0
  63. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/02_simple_control.py +0 -0
  64. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/03_waveforms.py +0 -0
  65. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/04_logging.py +0 -0
  66. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/06_pulse_effect.py +0 -0
  67. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/07_colorloop_effect.py +0 -0
  68. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/08_custom_effect.py +0 -0
  69. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/09_background_effect.py +0 -0
  70. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/10_find_specific_devices.py +0 -0
  71. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/11_matrix_basic.py +0 -0
  72. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/12_matrix_effects.py +0 -0
  73. {lifx_async-4.3.7 → lifx_async-4.3.9}/examples/13_matrix_large.py +0 -0
  74. {lifx_async-4.3.7 → lifx_async-4.3.9}/mkdocs.yml +0 -0
  75. {lifx_async-4.3.7 → lifx_async-4.3.9}/renovate.json +0 -0
  76. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/__init__.py +0 -0
  77. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/api.py +0 -0
  78. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/color.py +0 -0
  79. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/const.py +0 -0
  80. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/devices/__init__.py +0 -0
  81. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/devices/hev.py +0 -0
  82. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/devices/infrared.py +0 -0
  83. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/devices/light.py +0 -0
  84. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/devices/matrix.py +0 -0
  85. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/devices/multizone.py +0 -0
  86. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/effects/__init__.py +0 -0
  87. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/effects/base.py +0 -0
  88. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/effects/colorloop.py +0 -0
  89. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/effects/conductor.py +0 -0
  90. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/effects/const.py +0 -0
  91. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/effects/models.py +0 -0
  92. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/effects/pulse.py +0 -0
  93. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/effects/state_manager.py +0 -0
  94. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/exceptions.py +0 -0
  95. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/network/__init__.py +0 -0
  96. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/network/discovery.py +0 -0
  97. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/network/message.py +0 -0
  98. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/network/transport.py +0 -0
  99. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/products/__init__.py +0 -0
  100. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/products/generator.py +0 -0
  101. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/products/registry.py +0 -0
  102. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/protocol/__init__.py +0 -0
  103. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/protocol/base.py +0 -0
  104. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/protocol/generator.py +0 -0
  105. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/protocol/header.py +0 -0
  106. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/protocol/models.py +0 -0
  107. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/protocol/packets.py +0 -0
  108. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/protocol/protocol_types.py +0 -0
  109. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/protocol/serializer.py +0 -0
  110. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/py.typed +0 -0
  111. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/theme/__init__.py +0 -0
  112. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/theme/canvas.py +0 -0
  113. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/theme/generators.py +0 -0
  114. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/theme/library.py +0 -0
  115. {lifx_async-4.3.7 → lifx_async-4.3.9}/src/lifx/theme/theme.py +0 -0
  116. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/__init__.py +0 -0
  117. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_api/__init__.py +0 -0
  118. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/__init__.py +0 -0
  119. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/conftest.py +0 -0
  120. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/test_hev.py +0 -0
  121. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/test_infrared.py +0 -0
  122. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/test_light.py +0 -0
  123. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/test_mac_address.py +0 -0
  124. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/test_matrix.py +0 -0
  125. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_devices/test_multizone.py +0 -0
  126. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_effects/__init__.py +0 -0
  127. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_effects/test_base.py +0 -0
  128. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_effects/test_capability_filtering.py +0 -0
  129. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_effects/test_integration.py +0 -0
  130. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_effects/test_models.py +0 -0
  131. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_effects/test_pulse.py +0 -0
  132. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_effects/test_state_manager.py +0 -0
  133. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_network/__init__.py +0 -0
  134. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_network/test_message.py +0 -0
  135. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_network/test_message_advanced.py +0 -0
  136. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_products/test_product_generator.py +0 -0
  137. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_products/test_registry.py +0 -0
  138. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_protocol/test_generated.py +0 -0
  139. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_protocol/test_header.py +0 -0
  140. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_protocol/test_protocol_generator.py +0 -0
  141. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_theme/__init__.py +0 -0
  142. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_theme/conftest.py +0 -0
  143. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_theme/test_generators.py +0 -0
  144. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_theme/test_library.py +0 -0
  145. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_theme/test_theme.py +0 -0
  146. {lifx_async-4.3.7 → lifx_async-4.3.9}/tests/test_utils.py +0 -0
@@ -95,6 +95,7 @@ jobs:
95
95
  run: uv run --frozen pytest
96
96
 
97
97
  - name: Upload coverage to Codecov
98
+ if: matrix.os == 'ubuntu-latest'
98
99
  uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
99
100
  with:
100
101
  env_vars: OS,PYTHON
@@ -105,6 +106,7 @@ jobs:
105
106
  verbose: true
106
107
 
107
108
  - name: Upload test results to Codecov
109
+ if: matrix.os == 'ubuntu-latest'
108
110
  uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
109
111
  with:
110
112
  env_vars: OS,PYTHON
@@ -12,7 +12,7 @@ structures from a YAML specification.
12
12
  **Python Versions**: 3.11, 3.12, 3.13, 3.14 (tested on all versions via CI)
13
13
  **Runtime Dependencies**: Zero - completely dependency-free!
14
14
  **Async Framework**: Python's built-in `asyncio` (no external async library required)
15
- **Test Isolation**: lifx-emulator runs as subprocess, not a dependency
15
+ **Test Isolation**: lifx-emulator-core runs embedded in-process for fast, cross-platform testing
16
16
 
17
17
  ## Essential Commands
18
18
 
@@ -610,38 +610,27 @@ The `discover_devices()` function implements DoS protection through:
610
610
  Test files mirror source structure: `tests/test_devices/test_light.py` tests
611
611
  `src/lifx/devices/light.py`
612
612
 
613
- ### Integration Tests with lifx-emulator
613
+ ### Integration Tests with lifx-emulator-core
614
614
 
615
- Some tests require the `lifx-emulator` to run integration tests against real protocol implementations.
616
- The emulator runs as a **separate subprocess** and is **not** a dependency of lifx.
615
+ Some tests require `lifx-emulator-core` to run integration tests against real protocol implementations.
616
+ The emulator runs **embedded in-process** as a dev dependency, providing:
617
+ - Fast startup (~5-10ms vs 500ms+ for subprocess)
618
+ - Cross-platform support (Windows, macOS, Linux)
619
+ - Direct access to emulator internals for scenario testing
617
620
 
618
- **Setup Options**:
619
-
620
- 1. **Development setup** (recommended): Clone lifx-emulator as a sibling directory
621
- ```bash
622
- cd ..
623
- git clone https://github.com/Djelibeybi/lifx-emulator.git
624
- cd lifx-emulator
625
- uv sync
626
- cd ../lifx
627
- ```
628
-
629
- 2. **System install**: Install lifx-emulator globally (requires Python 3.13+)
630
- ```bash
631
- uv tool install lifx-emulator
632
- ```
621
+ **Setup**: The emulator is automatically installed as a dev dependency:
622
+ ```bash
623
+ uv sync # Installs lifx-emulator-core automatically
624
+ ```
633
625
 
634
626
  **Running Integration Tests**:
627
+ - Tests marked with `@pytest.mark.emulator` use the embedded emulator
635
628
  - If emulator is not available, these tests are automatically skipped
636
- - No code changes needed - pytest plugin handles everything
637
- - **Works on all Python versions (3.11+)** since emulator runs as separate process
638
-
639
- **Note**: The emulator itself requires Python 3.13+, but it runs as a subprocess so your
640
- lifx tests can run on any supported Python version (3.11-3.14).
629
+ - **Works on all Python versions (3.11+)**
641
630
 
642
631
  **External Emulator Management**:
643
632
 
644
- For cases where you want to manage the emulator separately (or test against actual hardware), you can skip the automatic emulator subprocess startup:
633
+ For cases where you want to manage the emulator separately (or test against actual hardware):
645
634
 
646
635
  ```bash
647
636
  # Use an externally managed emulator instance
@@ -654,7 +643,6 @@ LIFX_EMULATOR_EXTERNAL=1 pytest
654
643
  This is useful when:
655
644
  - Testing against actual LIFX hardware on your network
656
645
  - Running the emulator with custom configuration or device setup
657
- - Using a shared emulator instance across multiple test runs
658
646
  - Debugging emulator behavior separately from the test suite
659
647
 
660
648
  **Key Test Files:**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lifx-async
3
- Version: 4.3.7
3
+ Version: 4.3.9
4
4
  Summary: A modern, type-safe, async Python library for controlling LIFX lights
5
5
  Author-email: Avi Miller <me@dje.li>
6
6
  Maintainer-email: Avi Miller <me@dje.li>
@@ -2,6 +2,22 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v4.3.9 (2025-11-27)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **network**: Propagate timeout from request() to internal methods
10
+ ([`b35ebea`](https://github.com/Djelibeybi/lifx-async/commit/b35ebea46120bfd4ad9ce149f5e25125d3694b30))
11
+
12
+
13
+ ## v4.3.8 (2025-11-25)
14
+
15
+ ### Bug Fixes
16
+
17
+ - **network**: Raise exception on StateUnhandled instead of returning False
18
+ ([`5ca3e8a`](https://github.com/Djelibeybi/lifx-async/commit/5ca3e8abcde0ec0eefe77645aeb0a2e63b18418c))
19
+
20
+
5
21
  ## v4.3.7 (2025-11-25)
6
22
 
7
23
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lifx-async"
3
- version = "4.3.7"
3
+ version = "4.3.9"
4
4
  description = "A modern, type-safe, async Python library for controlling LIFX lights"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -31,7 +31,7 @@ classifiers = [
31
31
  [dependency-groups]
32
32
  dev = [
33
33
  "hatchling>=1.27.0",
34
- "lifx-emulator>=2.4.0",
34
+ "lifx-emulator-core>=3.0.3",
35
35
  "mkdocs-git-revision-date-localized-plugin>=1.4.7",
36
36
  "mkdocs-llmstxt>=0.4.0",
37
37
  "mkdocs-material>=9.6.22",
@@ -41,7 +41,6 @@ dev = [
41
41
  "pytest-asyncio>=0.24.0",
42
42
  "pytest-cov>=7.0.0",
43
43
  "pytest-sugar>=1.1.1",
44
- "pytest-xprocess>=1.0.2",
45
44
  "pyyaml>=6.0.3",
46
45
  "ruff>=0.14.2",
47
46
  ]
@@ -102,6 +101,9 @@ addopts = """\
102
101
  """
103
102
  asyncio_mode = "auto"
104
103
  asyncio_default_fixture_loop_scope = "function"
104
+ markers = [
105
+ "emulator: tests that require the lifx-emulator-core embedded emulator",
106
+ ]
105
107
 
106
108
  [tool.coverage.run]
107
109
  omit = [
@@ -166,8 +166,6 @@ class Device:
166
166
  raise LifxUnsupportedCommandError(
167
167
  f"Device does not support packet type {response.unhandled_type}"
168
168
  )
169
- if response is False:
170
- raise LifxUnsupportedCommandError("Device does not support this command")
171
169
 
172
170
  def __init__(
173
171
  self,
@@ -22,6 +22,7 @@ from lifx.exceptions import (
22
22
  LifxConnectionError,
23
23
  LifxProtocolError,
24
24
  LifxTimeoutError,
25
+ LifxUnsupportedCommandError,
25
26
  )
26
27
  from lifx.network.message import create_message, parse_message
27
28
  from lifx.network.transport import UdpTransport
@@ -722,8 +723,9 @@ class DeviceConnection:
722
723
 
723
724
  # Check for StateUnhandled - return False to indicate unsupported
724
725
  if header.pkt_type == _STATE_UNHANDLED_PKT_TYPE:
725
- yield False
726
- return
726
+ raise LifxUnsupportedCommandError(
727
+ "Device does not support this command"
728
+ )
727
729
 
728
730
  # ACK received successfully
729
731
  yield True
@@ -758,7 +760,7 @@ class DeviceConnection:
758
760
  async def request_stream(
759
761
  self,
760
762
  packet: Any,
761
- timeout: float = DEFAULT_REQUEST_TIMEOUT,
763
+ timeout: float | None = None,
762
764
  ) -> AsyncGenerator[Any, None]:
763
765
  """Send request and yield unpacked responses.
764
766
 
@@ -821,6 +823,9 @@ class DeviceConnection:
821
823
  # Ensure connection is open (lazy opening)
822
824
  await self._ensure_open()
823
825
 
826
+ if timeout is None:
827
+ timeout = self.timeout
828
+
824
829
  # Get packet metadata
825
830
  packet_kind = getattr(packet, "_packet_kind", "OTHER")
826
831
 
@@ -940,11 +945,7 @@ class DeviceConnection:
940
945
  f"Packet missing PKT_TYPE: {type(packet).__name__}"
941
946
  )
942
947
 
943
- async def request(
944
- self,
945
- packet: Any,
946
- timeout: float = DEFAULT_REQUEST_TIMEOUT,
947
- ) -> Any:
948
+ async def request(self, packet: Any, timeout: float | None = None) -> Any:
948
949
  """Send request and get single response (convenience wrapper).
949
950
 
950
951
  This is a convenience method that returns the first response from