phue2 0.0.1.dev280__tar.gz → 0.0.4__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 (42) hide show
  1. phue2-0.0.4/.cursor/rules/docs.mdc +14 -0
  2. phue2-0.0.4/.cursor/rules/python-style.mdc +36 -0
  3. {phue2-0.0.1.dev280 → phue2-0.0.4}/.github/workflows/lint.yml +1 -1
  4. {phue2-0.0.1.dev280 → phue2-0.0.4}/.github/workflows/publish.yml +1 -1
  5. phue2-0.0.4/CHANGELOG.md +64 -0
  6. {phue2-0.0.1.dev280 → phue2-0.0.4}/LICENSE +1 -1
  7. {phue2-0.0.1.dev280 → phue2-0.0.4}/PKG-INFO +7 -7
  8. {phue2-0.0.1.dev280 → phue2-0.0.4}/README.md +6 -6
  9. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/connect_bridge.py +1 -1
  10. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/flicker.py +1 -1
  11. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/hue-rainbow.py +1 -1
  12. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/random_colors.py +1 -1
  13. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/repros/smart_home.py +1 -1
  14. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/rgb_colors.py +1 -1
  15. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/sine_wave_lights.py +1 -1
  16. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/tk_gui_complex.py +1 -1
  17. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/tk_gui_hsb.py +1 -1
  18. {phue2-0.0.1.dev280 → phue2-0.0.4}/examples/tk_gui_simple.py +1 -1
  19. {phue2-0.0.1.dev280 → phue2-0.0.4}/pyproject.toml +4 -10
  20. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/__init__.py +2 -2
  21. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/__main__.py +50 -73
  22. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/bridge.py +15 -11
  23. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/group.py +2 -2
  24. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/light.py +1 -1
  25. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/sensor.py +1 -1
  26. phue2-0.0.4/tests/test_basic_import.py +7 -0
  27. {phue2-0.0.1.dev280 → phue2-0.0.4}/tests/test_bridge.py +34 -7
  28. {phue2-0.0.1.dev280 → phue2-0.0.4}/tests/test_cli.py +17 -39
  29. {phue2-0.0.1.dev280 → phue2-0.0.4}/tests/test_request.py +14 -14
  30. {phue2-0.0.1.dev280 → phue2-0.0.4}/tests/test_scene.py +1 -1
  31. {phue2-0.0.1.dev280 → phue2-0.0.4}/uv.lock +1 -1
  32. phue2-0.0.1.dev280/tests/test_basic_import.py +0 -7
  33. {phue2-0.0.1.dev280 → phue2-0.0.4}/.github/workflows/test.yml +0 -0
  34. {phue2-0.0.1.dev280 → phue2-0.0.4}/.gitignore +0 -0
  35. {phue2-0.0.1.dev280 → phue2-0.0.4}/.pre-commit-config.yaml +0 -0
  36. {phue2-0.0.1.dev280 → phue2-0.0.4}/justfile +0 -0
  37. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/_internal/__init__.py +0 -0
  38. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/_internal/console.py +0 -0
  39. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/exceptions.py +0 -0
  40. {phue2-0.0.1.dev280/src/phue2 → phue2-0.0.4/src/phue}/scene.py +0 -0
  41. {phue2-0.0.1.dev280 → phue2-0.0.4}/tests/__init__.py +0 -0
  42. {phue2-0.0.1.dev280 → phue2-0.0.4}/tests/samples.py +0 -0
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: style and tone for documentation
3
+ globs: *.mdx
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # tone
8
+ - casual, clear and concise
9
+ - use "you" to refer to the user (e.g. "Foo is great when you want Bar")
10
+
11
+ # content
12
+ - breathable
13
+ - makes good use of admonitions and mintlify's features like <CodeGroup> and <Tip>
14
+ - interspersed with practical and simple examples with output in <Accordian> where apt
@@ -0,0 +1,36 @@
1
+ ---
2
+ description: how to write, test and run python
3
+ globs: *.py
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Python Style
8
+
9
+ ## Design
10
+ - aggressively minimal and elegant
11
+ - prefer functional over OOP
12
+ - keep implementation details "private" (e.g. def _impl)
13
+
14
+ ## Type Hints
15
+ - Marvin supports Python 3.10+
16
+ - Use modern syntax and full type annotations
17
+ - Use | instead of Union
18
+ - Use builtins like list, dict instead of typing.List and typing.Dict
19
+ - use | None instead of Optional
20
+ - Always type optional keyword arguments correctly e.g. def f(x: int | None = None)
21
+
22
+ ## Running Commands
23
+ `uv` gives us superpowers to resolve deps fast and on the fly. no more pip or fussing with venvs
24
+
25
+ ### Install dependencies
26
+ - install project deps: `uv sync`
27
+ - install project deps with an extra: `uv sync --extra foo`
28
+
29
+ ### Running script
30
+ - run script with existing project deps: `uv run some/script.py`
31
+ - run script additional deps on the fly: `uv run --with pandas script.py`
32
+
33
+ ### Testing
34
+ - test all: `uv run pytest`
35
+ - test all with 3 runners: `uv run pytest -n3`
36
+ - test certain folder, only matching cases: `uv run pytest tests/basic -k some_test_fn_subtr`
@@ -25,7 +25,7 @@ jobs:
25
25
  - name: Set up Python
26
26
  uses: actions/setup-python@v5
27
27
  with:
28
- python-version-file: "pyproject.toml"
28
+ python-version: "3.13"
29
29
 
30
30
  - name: Install dependencies
31
31
  run: uv sync
@@ -1,4 +1,4 @@
1
- name: Publish phue2 to PyPI
1
+ name: Publish phue to PyPI
2
2
  on:
3
3
  release:
4
4
  types: [published]
@@ -0,0 +1,64 @@
1
+ # phue changelog
2
+
3
+ ## r12
4
+ - split functionality into modules
5
+ - added `httpx` dependency
6
+ - dropped support for python 2 and python <3.10
7
+ - updated packaging and tooling
8
+ - added complete type hints and lint checks
9
+
10
+ ## r11
11
+ - Add support for deleting scenes
12
+ - Various bug fixes
13
+
14
+ ## r10
15
+ - Misc bug fixes
16
+ - Better support for schedules
17
+
18
+ ## r9
19
+ - Added unit tests (sdague)
20
+ - Added scene support (sdague)
21
+ - Added sensor support (eldstal)
22
+ - Added reachable and type attributes to the Light object (carlosperate)
23
+ - Changed License to MIT
24
+
25
+ ## r8
26
+ - iOS compatibility (Nathanaël Lécaudé)
27
+ - Logging fixes
28
+ - Added effect changing options (bradykent)
29
+ - Several unicode fixes (Nathanaël Lécaudé)
30
+ - Misc bug fixes
31
+
32
+ ## r7
33
+ - Added to pypi
34
+ - Added support for Python 3 (Nathanaël Lécaudé)
35
+ - Logging level can be set with b.set_logging() (Nathanaël Lécaudé)
36
+ - Logging level can be set at init: b = Bridge(logging = 'debug') (Nathanaël Lécaudé)
37
+ - Added docstrings to Light properties (Nathanaël Lécaudé)
38
+ - Added colormode property to Light class (Nathanaël Lécaudé)
39
+ - IP is now optional if present in config file (Nathanaël Lécaudé)
40
+ - Implemented groups (Nathanaël Lécaudé)
41
+ - Implemented schedules (Nathanaël Lécaudé)
42
+ - Renamed get_info to get_api (Nathanaël Lécaudé)
43
+ - Renamed get_lights to get_light_objects (Nathanaël Lécaudé)
44
+ - Renamed set_state and get_state to set_light and get_light (Nathanaël Lécaudé)
45
+ - Fixed important bug when using set_state with a list of lights (Nathanaël Lécaudé)
46
+ - Add access to Light objects via direct indexing of the Bridge object via __getitem__ (Marshall Perrin)
47
+ - Implement real logging using Python's logging module, including error checking and display of responses from the server. (Marshall Perrin)
48
+ - Add function colortemp_k for color temperatures in Kelvin. (Marshall Perrin)
49
+ - Some additional error checking for invalid or missing parameters (Marshall Perrin)
50
+ - More details in docstrings. (Marshall Perrin)
51
+
52
+
53
+ ## r6
54
+ - Light objects are now obtained using the get_lights method
55
+ - Added the alert method to the Light object
56
+ - All requests now use httplib for consistency
57
+ - Moved all source to github
58
+ - Renamed the module to phue
59
+
60
+ ## r5
61
+ - Renamed the Bulb() object to Light() so it reflects the official API better
62
+ - You can now pass the username as argument to the Bridge class if you don't want to read/store to file
63
+ - You can now get the bridge name with brdige.name or set it with bridge.name = 'newname'
64
+ - The set_state method can now use a dictionary as first argument to send more complex messages
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2025 Nathan Nowack
3
+ Copyright (c) 2025 Nathan Nowack and Nathanaël Lécaudé
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: phue2
3
- Version: 0.0.1.dev280
3
+ Version: 0.0.4
4
4
  Summary: A Philips Hue Python library
5
5
  Author: Nathanaël Lécaudé, Nathan Nowack
6
6
  License: MIT
@@ -9,7 +9,7 @@ Requires-Python: >=3.10
9
9
  Requires-Dist: httpx
10
10
  Description-Content-Type: text/markdown
11
11
 
12
- # phue2
12
+ # phue
13
13
 
14
14
  modern Python library to control the Philips Hue lighting system
15
15
 
@@ -24,15 +24,15 @@ This is a fork of the original [phue library](https://github.com/studioimaginair
24
24
  ### Using uv
25
25
 
26
26
  ```bash
27
- uv add phue2
27
+ uv add phue
28
28
 
29
- uv pip install phue2
29
+ uv pip install phue
30
30
  ```
31
31
 
32
32
  ### Using pip
33
33
 
34
34
  ```bash
35
- pip install phue2
35
+ pip install phue
36
36
  ```
37
37
 
38
38
  ## Repository Structure
@@ -98,7 +98,7 @@ phue set group "Downstairs" --off
98
98
  Using the set_light and get_light methods you can control pretty much all the parameters:
99
99
 
100
100
  ```python
101
- from phue2 import Bridge
101
+ from phue import Bridge
102
102
 
103
103
  # Connect to the bridge
104
104
  b = Bridge('192.168.1.100')
@@ -163,7 +163,7 @@ for light_name in ['Kitchen', 'Bedroom', 'Garage']:
163
163
  The library provides custom exceptions for better error handling:
164
164
 
165
165
  ```python
166
- from phue2 import Bridge, PhueRegistrationException, PhueRequestTimeout
166
+ from phue import Bridge, PhueRegistrationException, PhueRequestTimeout
167
167
 
168
168
  try:
169
169
  b = Bridge('192.168.1.100')
@@ -1,4 +1,4 @@
1
- # phue2
1
+ # phue
2
2
 
3
3
  modern Python library to control the Philips Hue lighting system
4
4
 
@@ -13,15 +13,15 @@ This is a fork of the original [phue library](https://github.com/studioimaginair
13
13
  ### Using uv
14
14
 
15
15
  ```bash
16
- uv add phue2
16
+ uv add phue
17
17
 
18
- uv pip install phue2
18
+ uv pip install phue
19
19
  ```
20
20
 
21
21
  ### Using pip
22
22
 
23
23
  ```bash
24
- pip install phue2
24
+ pip install phue
25
25
  ```
26
26
 
27
27
  ## Repository Structure
@@ -87,7 +87,7 @@ phue set group "Downstairs" --off
87
87
  Using the set_light and get_light methods you can control pretty much all the parameters:
88
88
 
89
89
  ```python
90
- from phue2 import Bridge
90
+ from phue import Bridge
91
91
 
92
92
  # Connect to the bridge
93
93
  b = Bridge('192.168.1.100')
@@ -152,7 +152,7 @@ for light_name in ['Kitchen', 'Bedroom', 'Garage']:
152
152
  The library provides custom exceptions for better error handling:
153
153
 
154
154
  ```python
155
- from phue2 import Bridge, PhueRegistrationException, PhueRequestTimeout
155
+ from phue import Bridge, PhueRegistrationException, PhueRequestTimeout
156
156
 
157
157
  try:
158
158
  b = Bridge('192.168.1.100')
@@ -9,7 +9,7 @@ import logging
9
9
  import os
10
10
  import sys
11
11
 
12
- from phue2 import Bridge, Light, PhueRegistrationException, console
12
+ from phue import Bridge, Light, PhueRegistrationException, console
13
13
 
14
14
  logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s")
15
15
 
@@ -7,7 +7,7 @@ WARNING: If you have not previously connected to the bridge, run connect_bridge.
7
7
  import random
8
8
  from time import sleep
9
9
 
10
- from phue2 import Bridge
10
+ from phue import Bridge
11
11
 
12
12
  b = Bridge()
13
13
  b.connect()
@@ -7,7 +7,7 @@ WARNING: If you have not previously connected to the bridge, run connect_bridge.
7
7
 
8
8
  from time import sleep
9
9
 
10
- from phue2 import Bridge, Light
10
+ from phue import Bridge, Light
11
11
 
12
12
  b = Bridge() # Enter bridge IP here.
13
13
 
@@ -6,7 +6,7 @@ WARNING: If you have not previously connected to the bridge, run connect_bridge.
6
6
 
7
7
  import random
8
8
 
9
- from phue2 import Bridge
9
+ from phue import Bridge
10
10
 
11
11
  b = Bridge()
12
12
 
@@ -1,4 +1,4 @@
1
- from phue2 import Bridge
1
+ from phue import Bridge
2
2
 
3
3
  modes = {
4
4
  "main": {
@@ -4,7 +4,7 @@ This script demonstrates how to set the color of a light to a specific RGB color
4
4
  WARNING: If you have not previously connected to the bridge, run connect_bridge.py first.
5
5
  """
6
6
 
7
- from phue2 import Bridge
7
+ from phue import Bridge
8
8
 
9
9
 
10
10
  def rgb_to_xy(red: float, green: float, blue: float) -> list[float]:
@@ -7,7 +7,7 @@ WARNING: If you have not previously connected to the bridge, run connect_bridge.
7
7
  import math
8
8
  import time
9
9
 
10
- from phue2 import Bridge
10
+ from phue import Bridge
11
11
 
12
12
  # --- Configuration ---
13
13
  BRIDGE_IP = None # Replace with your bridge's IP address
@@ -17,7 +17,7 @@ from tkinter import (
17
17
  Tk,
18
18
  )
19
19
 
20
- from phue2 import Bridge
20
+ from phue import Bridge
21
21
 
22
22
 
23
23
  def scale_command(x: str, light_id: int) -> None:
@@ -17,7 +17,7 @@ from tkinter import (
17
17
  Tk,
18
18
  )
19
19
 
20
- from phue2 import Bridge
20
+ from phue import Bridge
21
21
 
22
22
  b = Bridge() # Enter bridge IP here.
23
23
 
@@ -11,7 +11,7 @@ from tkinter import (
11
11
  Tk,
12
12
  )
13
13
 
14
- from phue2 import Bridge
14
+ from phue import Bridge
15
15
 
16
16
  b = Bridge() # Enter bridge IP here.
17
17
 
@@ -9,7 +9,7 @@ readme = "README.md"
9
9
  license = { text = "MIT" }
10
10
 
11
11
  [project.scripts]
12
- phue = "phue2.__main__:main"
12
+ phue = "phue.__main__:main"
13
13
 
14
14
  [dependency-groups]
15
15
  dev = [
@@ -28,20 +28,14 @@ dev = [
28
28
 
29
29
 
30
30
  [build-system]
31
- requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
31
+ requires = ["hatchling", "hatch-vcs"]
32
32
  build-backend = "hatchling.build"
33
33
 
34
34
  [tool.hatch.build.targets.wheel]
35
- packages = ["src/phue2"]
35
+ packages = ["src/phue"]
36
36
 
37
37
  [tool.hatch.version]
38
- source = "uv-dynamic-versioning"
39
-
40
- [tool.uv-dynamic-versioning]
41
- vcs = "git"
42
- style = "pep440"
43
- bump = true
44
- metadata = false
38
+ source = "vcs"
45
39
 
46
40
 
47
41
  [tool.pytest.ini_options]
@@ -1,5 +1,5 @@
1
1
  """
2
- phue2 - A modernized Philips Hue Python library
2
+ phue - A modernized Philips Hue Python library
3
3
  Based on the original phue by Nathanaël Lécaudé
4
4
  Original protocol hacking by rsmck: http://rsmck.co.uk/hue
5
5
 
@@ -18,7 +18,7 @@ from ._internal.console import console
18
18
 
19
19
  import logging
20
20
 
21
- logger = logging.getLogger("phue2")
21
+ logger = logging.getLogger("phue")
22
22
 
23
23
 
24
24
  __all__ = [
@@ -6,8 +6,8 @@ import logging
6
6
  import os
7
7
  import sys
8
8
 
9
- from phue2 import Bridge, PhueRegistrationException
10
- from phue2._internal.console import (
9
+ from phue import Bridge, PhueRegistrationException
10
+ from phue._internal.console import (
11
11
  BLUE,
12
12
  BOLD,
13
13
  CYAN,
@@ -60,6 +60,12 @@ def parse_args(
60
60
  )
61
61
  parser.add_argument("--config-file-path", help="Path to the config file")
62
62
  parser.add_argument("--debug", action="store_true", help="Enable debug logging")
63
+ parser.add_argument(
64
+ "--no-save-config",
65
+ action="store_false",
66
+ dest="save_config",
67
+ help="Do not save connection details to the config file",
68
+ )
63
69
 
64
70
  # Command subparsers
65
71
  subparsers = parser.add_subparsers(dest="command", help="Command to execute")
@@ -161,7 +167,11 @@ def main(argv: list[str] | None = None) -> int:
161
167
  console.info(f"Connecting to bridge at {args.host}...")
162
168
  while True:
163
169
  try:
164
- bridge = Bridge(args.host, config_file_path=args.config_file_path)
170
+ bridge = Bridge(
171
+ args.host,
172
+ config_file_path=args.config_file_path,
173
+ save_config=args.save_config,
174
+ )
165
175
  console.success("Successfully connected to the bridge!")
166
176
  break
167
177
  except PhueRegistrationException:
@@ -243,34 +253,30 @@ def main(argv: list[str] | None = None) -> int:
243
253
  name = args.name
244
254
 
245
255
  if resource == "light":
246
- # Try by ID first, then by name
247
- light = None
248
256
  try:
249
- light_id = int(name)
250
- light = bridge.lights_by_id.get(light_id)
251
- except ValueError:
252
- pass
253
-
254
- if not light:
255
- light = bridge.lights_by_name.get(name)
256
-
257
- if not light:
257
+ light = bridge.get_light(name)
258
+ assert light["name"]
259
+ except KeyError:
258
260
  console.error(f"Light '{name}' not found")
259
261
  return 1
260
262
 
261
- console.info(styled_for_cli(f"LIGHT: {light.name}", YELLOW + BOLD))
263
+ console.info(styled_for_cli(f"LIGHT: {light['name']}", YELLOW + BOLD))
264
+ console.info(
265
+ f" {styled_for_cli('Status:', BLUE)} {styled_for_cli('ON' if light['state']['on'] else 'OFF', GREEN if light['state']['on'] else RED)}"
266
+ )
267
+ console.info(f" {styled_for_cli('Type:', BLUE)} {light['type']}")
262
268
  console.info(
263
- f" {styled_for_cli('Status:', BLUE)} {styled_for_cli('ON' if light.on else 'OFF', GREEN if light.on else RED)}"
269
+ f" {styled_for_cli('Brightness:', BLUE)} {light['state']['bri']}/254"
264
270
  )
265
- console.info(f" {styled_for_cli('Type:', BLUE)} {light.type}")
266
271
  console.info(
267
- f" {styled_for_cli('Brightness:', BLUE)} {light.brightness}/254"
272
+ f" {styled_for_cli('Hue:', BLUE)} {light['state']['hue']}/65535"
268
273
  )
269
- console.info(f" {styled_for_cli('Hue:', BLUE)} {light.hue}/65535")
270
274
  console.info(
271
- f" {styled_for_cli('Saturation:', BLUE)} {light.saturation}/254"
275
+ f" {styled_for_cli('Saturation:', BLUE)} {light['state']['sat']}/254"
276
+ )
277
+ console.info(
278
+ f" {styled_for_cli('Reachable:', BLUE)} {light['state']['reachable']}"
272
279
  )
273
- console.info(f" {styled_for_cli('Reachable:', BLUE)} {light.reachable}")
274
280
 
275
281
  elif resource == "group":
276
282
  # Try to find group by name
@@ -312,45 +318,39 @@ def main(argv: list[str] | None = None) -> int:
312
318
  resource = args.resource
313
319
  name = args.name
314
320
  changed = False
321
+ state_changes: dict[str, object] = {}
322
+
323
+ if args.on:
324
+ state_changes["on"] = True
325
+ changed = True
326
+ elif args.off:
327
+ state_changes["on"] = False
328
+ changed = True
329
+
330
+ if args.bri is not None:
331
+ state_changes["bri"] = args.bri
332
+ changed = True
333
+
334
+ if args.hue is not None:
335
+ state_changes["hue"] = args.hue
336
+ changed = True
337
+
338
+ if args.sat is not None:
339
+ state_changes["sat"] = args.sat
340
+ changed = True
315
341
 
316
342
  if resource == "light":
317
- # Try by ID first, then by name
318
343
  light = None
319
- try:
320
- light_id = int(name)
321
- light = bridge.lights_by_id.get(light_id)
322
- except ValueError:
323
- pass
324
-
325
344
  if not light:
326
- light = bridge.lights_by_name.get(name)
345
+ light = bridge.get_light(name)
327
346
 
328
347
  if not light:
329
348
  console.error(f"Light '{name}' not found")
330
349
  return 1
331
350
 
332
- # Apply state changes
333
- if args.on:
334
- light.on = True
335
- changed = True
336
- elif args.off:
337
- light.on = False
338
- changed = True
339
-
340
- if args.bri is not None:
341
- light.brightness = args.bri
342
- changed = True
343
-
344
- if args.hue is not None:
345
- light.hue = args.hue
346
- changed = True
347
-
348
- if args.sat is not None:
349
- light.saturation = args.sat
350
- changed = True
351
-
352
351
  if changed:
353
- console.success(f"Updated light '{light.name}'")
352
+ bridge.set_light(name, state_changes)
353
+ console.success(f"Updated light '{light['name']}'")
354
354
  else:
355
355
  console.warning("No changes specified")
356
356
 
@@ -372,29 +372,6 @@ def main(argv: list[str] | None = None) -> int:
372
372
  console.error(f"Group '{name}' not found")
373
373
  return 1
374
374
 
375
- # Apply state changes
376
- state_changes: dict[str, object] = {}
377
- changed = False
378
-
379
- if args.on:
380
- state_changes["on"] = True
381
- changed = True
382
- elif args.off:
383
- state_changes["on"] = False
384
- changed = True
385
-
386
- if args.bri is not None:
387
- state_changes["bri"] = args.bri
388
- changed = True
389
-
390
- if args.hue is not None:
391
- state_changes["hue"] = args.hue
392
- changed = True
393
-
394
- if args.sat is not None:
395
- state_changes["sat"] = args.sat
396
- changed = True
397
-
398
375
  if changed:
399
376
  bridge.set_group(group_id, state_changes)
400
377
  console.success(f"Updated group {name}")
@@ -9,15 +9,15 @@ from typing import TYPE_CHECKING, Any, Literal, cast, overload
9
9
 
10
10
  import httpx
11
11
 
12
- from phue2.exceptions import (
12
+ from phue.exceptions import (
13
13
  PhueException,
14
14
  PhueRegistrationException,
15
15
  PhueRequestTimeout,
16
16
  )
17
- from phue2.group import Group
18
- from phue2.light import Light
19
- from phue2.scene import Scene
20
- from phue2.sensor import Sensor
17
+ from phue.group import Group
18
+ from phue.light import Light
19
+ from phue.scene import Scene
20
+ from phue.sensor import Sensor
21
21
 
22
22
  logger = logging.getLogger("phue_modern")
23
23
 
@@ -46,6 +46,7 @@ class Bridge:
46
46
  username: str | None = None,
47
47
  config_file_path: str | None = None,
48
48
  timeout: int = 10,
49
+ save_config: bool = True,
49
50
  ):
50
51
  """Initialization function.
51
52
 
@@ -54,6 +55,7 @@ class Bridge:
54
55
  username: Optional username for the bridge
55
56
  config_file_path: Optional path to the configuration file
56
57
  timeout: Request timeout in seconds (default: 10)
58
+ save_config: If False, don't save the config file (default: True)
57
59
  """
58
60
  # Determine config file path
59
61
  if config_file_path is not None:
@@ -89,6 +91,7 @@ class Bridge:
89
91
  self.ip = ip
90
92
  self._username = username
91
93
  self.timeout = timeout
94
+ self.save_config = save_config
92
95
  self.lights_by_id: dict[int, Light] = {}
93
96
  self.lights_by_name: dict[str, Light] = {}
94
97
  self.sensors_by_id: dict[int, Sensor] = {}
@@ -233,12 +236,13 @@ class Bridge:
233
236
  for line in response:
234
237
  for key in line:
235
238
  if "success" in key:
236
- with open(self.config_file_path, "w") as f:
237
- logger.info(
238
- "Writing configuration file to " + self.config_file_path
239
- )
240
- f.write(json.dumps({self.ip: line["success"]}))
241
- logger.info("Reconnecting to the bridge")
239
+ if self.save_config:
240
+ with open(self.config_file_path, "w") as f:
241
+ logger.info(
242
+ "Writing configuration file to " + self.config_file_path
243
+ )
244
+ f.write(json.dumps({self.ip: line["success"]}))
245
+ logger.info("Reconnecting to the bridge")
242
246
  self._username = line["success"]["username"]
243
247
  return
244
248
  if "error" in key:
@@ -3,12 +3,12 @@
3
3
  import logging
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
- from phue2.light import Light
6
+ from phue.light import Light
7
7
 
8
8
  logger = logging.getLogger("phue_modern")
9
9
 
10
10
  if TYPE_CHECKING:
11
- from phue2.bridge import Bridge
11
+ from phue.bridge import Bridge
12
12
 
13
13
 
14
14
  class Group(Light):
@@ -4,7 +4,7 @@ import logging
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
6
  if TYPE_CHECKING:
7
- from phue2.bridge import Bridge
7
+ from phue.bridge import Bridge
8
8
 
9
9
  logger = logging.getLogger("phue_modern")
10
10
 
@@ -6,7 +6,7 @@ from collections.abc import Callable
6
6
  from typing import TYPE_CHECKING, Any, TypeAlias
7
7
 
8
8
  if TYPE_CHECKING:
9
- from phue2 import Bridge
9
+ from phue import Bridge
10
10
 
11
11
  logger = logging.getLogger("phue_modern")
12
12
 
@@ -0,0 +1,7 @@
1
+ from types import ModuleType
2
+
3
+
4
+ def test_import_works():
5
+ import phue
6
+
7
+ assert isinstance(phue, ModuleType)
@@ -4,7 +4,7 @@ import httpx
4
4
  import pytest
5
5
  import respx
6
6
 
7
- import phue2
7
+ import phue
8
8
 
9
9
 
10
10
  @pytest.fixture
@@ -23,7 +23,7 @@ def test_bridge_discovery_and_registration(tmp_config_path: str) -> None:
23
23
  )
24
24
 
25
25
  # Create a bridge directly with IP and username, but use temp config
26
- bridge = phue2.Bridge(
26
+ bridge = phue.Bridge(
27
27
  ip="192.168.1.100", username="testuser", config_file_path=tmp_config_path
28
28
  )
29
29
 
@@ -51,7 +51,7 @@ def test_light_control(tmp_config_path: str) -> None:
51
51
  )
52
52
  )
53
53
 
54
- bridge = phue2.Bridge(
54
+ bridge = phue.Bridge(
55
55
  ip="192.168.1.100", username="testuser", config_file_path=tmp_config_path
56
56
  )
57
57
  light = bridge.lights[0]
@@ -64,11 +64,11 @@ def test_error_handling(tmp_config_path: str) -> None:
64
64
  side_effect=httpx.TimeoutException("Connection timed out")
65
65
  )
66
66
 
67
- bridge = phue2.Bridge(
67
+ bridge = phue.Bridge(
68
68
  ip="192.168.1.100", username="testuser", config_file_path=tmp_config_path
69
69
  )
70
70
 
71
- with pytest.raises(phue2.PhueRequestTimeout):
71
+ with pytest.raises(phue.PhueRequestTimeout):
72
72
  bridge.get_light()
73
73
 
74
74
 
@@ -83,10 +83,37 @@ def test_config_file(tmp_path: Path) -> None:
83
83
  )
84
84
 
85
85
  # Create a bridge and verify config file is created
86
- bridge1 = phue2.Bridge(ip="192.168.1.100", config_file_path=str(config_file))
86
+ bridge1 = phue.Bridge(ip="192.168.1.100", config_file_path=str(config_file))
87
87
  assert config_file.exists()
88
88
  assert bridge1.username == "testuser"
89
89
 
90
90
  # Test loading from config
91
- bridge2 = phue2.Bridge(config_file_path=str(config_file))
91
+ bridge2 = phue.Bridge(config_file_path=str(config_file))
92
92
  assert bridge2.username == "testuser"
93
+
94
+
95
+ def test_no_save_config(tmp_path: Path) -> None:
96
+ """Test that the config file is not saved when save_config is False."""
97
+ config_file = tmp_path / ".python_hue"
98
+
99
+ with respx.mock(assert_all_called=True) as mock:
100
+ mock.post("http://192.168.1.100/api", name="create_user").mock(
101
+ return_value=httpx.Response(
102
+ 200, json=[{"success": {"username": "nosavetest"}}]
103
+ )
104
+ )
105
+
106
+ # Create a bridge with save_config=False
107
+ bridge = phue.Bridge(
108
+ ip="192.168.1.100",
109
+ config_file_path=str(config_file),
110
+ save_config=False,
111
+ )
112
+
113
+ # Verify config file was NOT created and username is set
114
+ assert not config_file.exists()
115
+ assert bridge.username == "nosavetest"
116
+
117
+ # Verify that trying to connect again without IP fails (as config wasn't saved)
118
+ with pytest.raises(phue.PhueException):
119
+ phue.Bridge(config_file_path=str(config_file))
@@ -7,13 +7,13 @@ import httpx
7
7
  import pytest
8
8
  import respx
9
9
 
10
- from phue2 import Bridge
11
- from phue2.__main__ import main
10
+ from phue import Bridge
11
+ from phue.__main__ import main
12
12
 
13
13
 
14
14
  @pytest.fixture(autouse=True)
15
15
  def disable_styling(monkeypatch: pytest.MonkeyPatch) -> None:
16
- monkeypatch.setattr("phue2.__main__.DISABLE_STYLING", True)
16
+ monkeypatch.setattr("phue.__main__.DISABLE_STYLING", True)
17
17
 
18
18
 
19
19
  def test_cli_help() -> None:
@@ -27,8 +27,8 @@ def test_cli_help() -> None:
27
27
 
28
28
  def test_cli_missing_args() -> None:
29
29
  """Test that the CLI errors when no host or command is provided but no config exists."""
30
- with patch("phue2.__main__.get_bridge_from_config", return_value=None):
31
- with patch("phue2._internal.console.console.error") as mock_error:
30
+ with patch("phue.__main__.get_bridge_from_config", return_value=None):
31
+ with patch("phue._internal.console.console.error") as mock_error:
32
32
  result = main([])
33
33
  assert result == 1
34
34
  mock_error.assert_called_once()
@@ -46,7 +46,7 @@ def test_cli_successful_connection(tmp_path: Path) -> None:
46
46
  )
47
47
 
48
48
  # Run the CLI with mocked input for the link button prompt
49
- with patch("phue2._internal.console.console.success") as mock_success:
49
+ with patch("phue._internal.console.console.success") as mock_success:
50
50
  result = main(
51
51
  ["--host", "192.168.1.100", "--config-file-path", str(config_path)]
52
52
  )
@@ -75,8 +75,8 @@ def test_cli_retry_on_registration_exception(tmp_path: Path) -> None:
75
75
  # Mock the input function to simulate pressing the button
76
76
  with (
77
77
  patch("builtins.input", return_value=""),
78
- patch("phue2._internal.console.console.warning") as mock_warning,
79
- patch("phue2._internal.console.console.success") as mock_success,
78
+ patch("phue._internal.console.console.warning") as mock_warning,
79
+ patch("phue._internal.console.console.success") as mock_success,
80
80
  ):
81
81
  result = main(
82
82
  ["--host", "192.168.1.100", "--config-file-path", str(config_path)]
@@ -105,9 +105,9 @@ def test_cli_list_command(resource: str, attribute: str) -> None:
105
105
  setattr(mock_bridge, attribute, mock_items)
106
106
 
107
107
  with (
108
- patch("phue2.__main__.Bridge", return_value=mock_bridge),
109
- patch("phue2.__main__.get_bridge_from_config", return_value=None),
110
- patch("phue2._internal.console.console.info") as mock_info,
108
+ patch("phue.__main__.Bridge", return_value=mock_bridge),
109
+ patch("phue.__main__.get_bridge_from_config", return_value=None),
110
+ patch("phue._internal.console.console.info") as mock_info,
111
111
  ):
112
112
  result = main(["--host", "192.168.1.100", "list", resource])
113
113
 
@@ -118,51 +118,29 @@ def test_cli_list_command(resource: str, attribute: str) -> None:
118
118
 
119
119
  def test_cli_get_command() -> None:
120
120
  """Test retrieving the details of a light."""
121
- # Create mock Bridge and light
122
121
  mock_bridge = MagicMock(spec=Bridge)
123
- mock_light = MagicMock()
124
- # Set properties to real values, not mocks
125
- mock_light.name = "Test Light"
126
- mock_light.on = True
127
- mock_light.brightness = 254
128
- mock_light.hue = 10000
129
- mock_light.saturation = 200
130
- mock_light.type = "Extended color light"
131
- mock_light.reachable = True
132
- mock_bridge.lights_by_name = {"Test Light": mock_light}
133
- mock_bridge.lights_by_id = {}
134
122
 
135
123
  with (
136
- patch("phue2.__main__.Bridge", return_value=mock_bridge),
137
- patch("phue2.__main__.get_bridge_from_config", return_value=None),
138
- patch("phue2._internal.console.console.info") as mock_info,
124
+ patch("phue.__main__.Bridge", return_value=mock_bridge),
125
+ patch("phue._internal.console.console.info") as mock_info,
139
126
  ):
140
127
  result = main(["--host", "192.168.1.100", "get", "light", "Test Light"])
141
128
 
142
129
  assert result == 0
143
- mock_info.assert_any_call("LIGHT: Test Light")
130
+ mock_info.assert_called()
144
131
 
145
132
 
146
133
  def test_cli_set_command() -> None:
147
134
  """Test setting the state of a light."""
148
- # Create mock Bridge and light
149
135
  mock_bridge = MagicMock(spec=Bridge)
150
- mock_light = MagicMock()
151
- # Set the name property to a string, not a mock
152
- mock_light.name = "Test Light"
153
- mock_bridge.lights_by_id = {1: mock_light}
154
- mock_bridge.lights_by_name = {}
155
136
 
156
137
  with (
157
- patch("phue2.__main__.Bridge", return_value=mock_bridge),
158
- patch("phue2.__main__.get_bridge_from_config", return_value=None),
159
- patch("phue2._internal.console.console.success") as mock_success,
138
+ patch("phue.__main__.Bridge", return_value=mock_bridge),
139
+ patch("phue._internal.console.console.success") as mock_success,
160
140
  ):
161
141
  result = main(
162
142
  ["--host", "192.168.1.100", "set", "light", "1", "--on", "--bri", "200"]
163
143
  )
164
144
 
165
145
  assert result == 0
166
- mock_success.assert_called_with("Updated light 'Test Light'")
167
- assert mock_light.on is True
168
- assert mock_light.brightness == 200
146
+ mock_success.assert_called()
@@ -11,7 +11,7 @@ from unittest import mock
11
11
 
12
12
  import pytest
13
13
 
14
- import phue2
14
+ import phue
15
15
 
16
16
 
17
17
  @pytest.fixture
@@ -27,9 +27,9 @@ def temp_home(tmp_path: Path) -> Generator[Path, None, None]:
27
27
  def test_register(temp_home: Path):
28
28
  """test that registration happens automatically during setup."""
29
29
  confname = os.path.join(temp_home, ".python_hue")
30
- with mock.patch("phue2.Bridge.request") as req:
30
+ with mock.patch("phue.Bridge.request") as req:
31
31
  req.return_value = [{"success": {"username": "fooo"}}]
32
- bridge = phue2.Bridge(ip="10.0.0.0")
32
+ bridge = phue.Bridge(ip="10.0.0.0")
33
33
  assert bridge.config_file_path == confname
34
34
 
35
35
  # check contents of file
@@ -38,35 +38,35 @@ def test_register(temp_home: Path):
38
38
  assert contents == '{"10.0.0.0": {"username": "fooo"}}'
39
39
 
40
40
  # make sure we can open under a different file
41
- bridge2 = phue2.Bridge(ip="10.0.0.0")
41
+ bridge2 = phue.Bridge(ip="10.0.0.0")
42
42
  assert bridge2.username == "fooo"
43
43
 
44
44
  # and that we can even open without an ip address
45
- bridge3 = phue2.Bridge()
45
+ bridge3 = phue.Bridge()
46
46
  assert bridge3.username == "fooo"
47
47
  assert bridge3.ip == "10.0.0.0"
48
48
 
49
49
 
50
50
  def test_register_fail():
51
51
  """Test that registration fails in the expected way for timeout"""
52
- with mock.patch("phue2.Bridge.request") as req:
52
+ with mock.patch("phue.Bridge.request") as req:
53
53
  req.return_value = [{"error": {"type": 101}}]
54
- with pytest.raises(phue2.PhueRegistrationException):
55
- phue2.Bridge(ip="10.0.0.0")
54
+ with pytest.raises(phue.PhueRegistrationException):
55
+ phue.Bridge(ip="10.0.0.0")
56
56
 
57
57
 
58
58
  def test_register_unknown_user():
59
59
  """Test that registration for unknown user works."""
60
- with mock.patch("phue2.Bridge.request") as req:
60
+ with mock.patch("phue.Bridge.request") as req:
61
61
  req.return_value = [{"error": {"type": 7}}]
62
- with pytest.raises(phue2.PhueException):
63
- phue2.Bridge(ip="10.0.0.0")
62
+ with pytest.raises(phue.PhueException):
63
+ phue.Bridge(ip="10.0.0.0")
64
64
 
65
65
 
66
66
  @pytest.fixture
67
67
  def mock_bridge():
68
68
  """Fixture that provides a bridge with mocked request method."""
69
- with mock.patch("phue2.Bridge.request") as mock_request:
69
+ with mock.patch("phue.Bridge.request") as mock_request:
70
70
  # Mock the lights collection endpoint
71
71
 
72
72
  def mock_request_side_effect(
@@ -93,11 +93,11 @@ def mock_bridge():
93
93
 
94
94
  mock_request.side_effect = mock_request_side_effect
95
95
 
96
- bridge = phue2.Bridge(ip="10.0.0.0", username="username")
96
+ bridge = phue.Bridge(ip="10.0.0.0", username="username")
97
97
  yield bridge
98
98
 
99
99
 
100
- def test_get_lights(mock_bridge: phue2.Bridge):
100
+ def test_get_lights(mock_bridge: phue.Bridge):
101
101
  """Test getting light objects by ID."""
102
102
  lights = mock_bridge.get_light_objects("id")
103
103
  assert lights[1].name == "Living Room Bulb"
@@ -1,6 +1,6 @@
1
1
  """Test the Scene class."""
2
2
 
3
- from phue2.scene import Scene
3
+ from phue.scene import Scene
4
4
 
5
5
 
6
6
  def test_scene_creation():
@@ -481,7 +481,7 @@ wheels = [
481
481
  ]
482
482
 
483
483
  [[package]]
484
- name = "phue2"
484
+ name = "phue"
485
485
  source = { editable = "." }
486
486
  dependencies = [
487
487
  { name = "httpx" },
@@ -1,7 +0,0 @@
1
- from types import ModuleType
2
-
3
-
4
- def test_import_works():
5
- import phue2
6
-
7
- assert isinstance(phue2, ModuleType)
File without changes
File without changes
File without changes
File without changes