mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
Update cli to use common modules and remove iot specific cli testing (#913)
This commit is contained in:
parent
ef49f44eac
commit
67b5d7de83
74
kasa/cli.py
74
kasa/cli.py
@ -27,7 +27,7 @@ from kasa import (
|
|||||||
EncryptType,
|
EncryptType,
|
||||||
Feature,
|
Feature,
|
||||||
KasaException,
|
KasaException,
|
||||||
Light,
|
Module,
|
||||||
UnsupportedDeviceError,
|
UnsupportedDeviceError,
|
||||||
)
|
)
|
||||||
from kasa.discover import DiscoveryResult
|
from kasa.discover import DiscoveryResult
|
||||||
@ -859,18 +859,18 @@ async def usage(dev: Device, year, month, erase):
|
|||||||
@click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False)
|
@click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False)
|
||||||
@click.option("--transition", type=int, required=False)
|
@click.option("--transition", type=int, required=False)
|
||||||
@pass_dev
|
@pass_dev
|
||||||
async def brightness(dev: Light, brightness: int, transition: int):
|
async def brightness(dev: Device, brightness: int, transition: int):
|
||||||
"""Get or set brightness."""
|
"""Get or set brightness."""
|
||||||
if not dev.is_dimmable:
|
if not (light := dev.modules.get(Module.Light)) or not light.is_dimmable:
|
||||||
echo("This device does not support brightness.")
|
echo("This device does not support brightness.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if brightness is None:
|
if brightness is None:
|
||||||
echo(f"Brightness: {dev.brightness}")
|
echo(f"Brightness: {light.brightness}")
|
||||||
return dev.brightness
|
return light.brightness
|
||||||
else:
|
else:
|
||||||
echo(f"Setting brightness to {brightness}")
|
echo(f"Setting brightness to {brightness}")
|
||||||
return await dev.set_brightness(brightness, transition=transition)
|
return await light.set_brightness(brightness, transition=transition)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -879,15 +879,15 @@ async def brightness(dev: Light, brightness: int, transition: int):
|
|||||||
)
|
)
|
||||||
@click.option("--transition", type=int, required=False)
|
@click.option("--transition", type=int, required=False)
|
||||||
@pass_dev
|
@pass_dev
|
||||||
async def temperature(dev: Light, temperature: int, transition: int):
|
async def temperature(dev: Device, temperature: int, transition: int):
|
||||||
"""Get or set color temperature."""
|
"""Get or set color temperature."""
|
||||||
if not dev.is_variable_color_temp:
|
if not (light := dev.modules.get(Module.Light)) or not light.is_variable_color_temp:
|
||||||
echo("Device does not support color temperature")
|
echo("Device does not support color temperature")
|
||||||
return
|
return
|
||||||
|
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
echo(f"Color temperature: {dev.color_temp}")
|
echo(f"Color temperature: {light.color_temp}")
|
||||||
valid_temperature_range = dev.valid_temperature_range
|
valid_temperature_range = light.valid_temperature_range
|
||||||
if valid_temperature_range != (0, 0):
|
if valid_temperature_range != (0, 0):
|
||||||
echo("(min: {}, max: {})".format(*valid_temperature_range))
|
echo("(min: {}, max: {})".format(*valid_temperature_range))
|
||||||
else:
|
else:
|
||||||
@ -895,31 +895,34 @@ async def temperature(dev: Light, temperature: int, transition: int):
|
|||||||
"Temperature range unknown, please open a github issue"
|
"Temperature range unknown, please open a github issue"
|
||||||
f" or a pull request for model '{dev.model}'"
|
f" or a pull request for model '{dev.model}'"
|
||||||
)
|
)
|
||||||
return dev.valid_temperature_range
|
return light.valid_temperature_range
|
||||||
else:
|
else:
|
||||||
echo(f"Setting color temperature to {temperature}")
|
echo(f"Setting color temperature to {temperature}")
|
||||||
return await dev.set_color_temp(temperature, transition=transition)
|
return await light.set_color_temp(temperature, transition=transition)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.argument("effect", type=click.STRING, default=None, required=False)
|
@click.argument("effect", type=click.STRING, default=None, required=False)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@pass_dev
|
@pass_dev
|
||||||
async def effect(dev, ctx, effect):
|
async def effect(dev: Device, ctx, effect):
|
||||||
"""Set an effect."""
|
"""Set an effect."""
|
||||||
if not dev.has_effects:
|
if not (light_effect := dev.modules.get(Module.LightEffect)):
|
||||||
echo("Device does not support effects")
|
echo("Device does not support effects")
|
||||||
return
|
return
|
||||||
if effect is None:
|
if effect is None:
|
||||||
raise click.BadArgumentUsage(
|
raise click.BadArgumentUsage(
|
||||||
f"Setting an effect requires a named built-in effect: {dev.effect_list}",
|
"Setting an effect requires a named built-in effect: "
|
||||||
|
+ f"{light_effect.effect_list}",
|
||||||
ctx,
|
ctx,
|
||||||
)
|
)
|
||||||
if effect not in dev.effect_list:
|
if effect not in light_effect.effect_list:
|
||||||
raise click.BadArgumentUsage(f"Effect must be one of: {dev.effect_list}", ctx)
|
raise click.BadArgumentUsage(
|
||||||
|
f"Effect must be one of: {light_effect.effect_list}", ctx
|
||||||
|
)
|
||||||
|
|
||||||
echo(f"Setting Effect: {effect}")
|
echo(f"Setting Effect: {effect}")
|
||||||
return await dev.set_effect(effect)
|
return await light_effect.set_effect(effect)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -929,33 +932,36 @@ async def effect(dev, ctx, effect):
|
|||||||
@click.option("--transition", type=int, required=False)
|
@click.option("--transition", type=int, required=False)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@pass_dev
|
@pass_dev
|
||||||
async def hsv(dev, ctx, h, s, v, transition):
|
async def hsv(dev: Device, ctx, h, s, v, transition):
|
||||||
"""Get or set color in HSV."""
|
"""Get or set color in HSV."""
|
||||||
if not dev.is_color:
|
if not (light := dev.modules.get(Module.Light)) or not light.is_color:
|
||||||
echo("Device does not support colors")
|
echo("Device does not support colors")
|
||||||
return
|
return
|
||||||
|
|
||||||
if h is None or s is None or v is None:
|
if h is None and s is None and v is None:
|
||||||
echo(f"Current HSV: {dev.hsv}")
|
echo(f"Current HSV: {light.hsv}")
|
||||||
return dev.hsv
|
return light.hsv
|
||||||
elif s is None or v is None:
|
elif s is None or v is None:
|
||||||
raise click.BadArgumentUsage("Setting a color requires 3 values.", ctx)
|
raise click.BadArgumentUsage("Setting a color requires 3 values.", ctx)
|
||||||
else:
|
else:
|
||||||
echo(f"Setting HSV: {h} {s} {v}")
|
echo(f"Setting HSV: {h} {s} {v}")
|
||||||
return await dev.set_hsv(h, s, v, transition=transition)
|
return await light.set_hsv(h, s, v, transition=transition)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.argument("state", type=bool, required=False)
|
@click.argument("state", type=bool, required=False)
|
||||||
@pass_dev
|
@pass_dev
|
||||||
async def led(dev, state):
|
async def led(dev: Device, state):
|
||||||
"""Get or set (Plug's) led state."""
|
"""Get or set (Plug's) led state."""
|
||||||
|
if not (led := dev.modules.get(Module.Led)):
|
||||||
|
echo("Device does not support led.")
|
||||||
|
return
|
||||||
if state is not None:
|
if state is not None:
|
||||||
echo(f"Turning led to {state}")
|
echo(f"Turning led to {state}")
|
||||||
return await dev.set_led(state)
|
return await led.set_led(state)
|
||||||
else:
|
else:
|
||||||
echo(f"LED state: {dev.led}")
|
echo(f"LED state: {led.led}")
|
||||||
return dev.led
|
return led.led
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -975,8 +981,8 @@ async def time(dev):
|
|||||||
async def on(dev: Device, index: int, name: str, transition: int):
|
async def on(dev: Device, index: int, name: str, transition: int):
|
||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
if index is not None or name is not None:
|
if index is not None or name is not None:
|
||||||
if not dev.is_strip:
|
if not dev.children:
|
||||||
echo("Index and name are only for power strips!")
|
echo("Index and name are only for devices with children.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if index is not None:
|
if index is not None:
|
||||||
@ -996,8 +1002,8 @@ async def on(dev: Device, index: int, name: str, transition: int):
|
|||||||
async def off(dev: Device, index: int, name: str, transition: int):
|
async def off(dev: Device, index: int, name: str, transition: int):
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
if index is not None or name is not None:
|
if index is not None or name is not None:
|
||||||
if not dev.is_strip:
|
if not dev.children:
|
||||||
echo("Index and name are only for power strips!")
|
echo("Index and name are only for devices with children.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if index is not None:
|
if index is not None:
|
||||||
@ -1017,8 +1023,8 @@ async def off(dev: Device, index: int, name: str, transition: int):
|
|||||||
async def toggle(dev: Device, index: int, name: str, transition: int):
|
async def toggle(dev: Device, index: int, name: str, transition: int):
|
||||||
"""Toggle the device on/off."""
|
"""Toggle the device on/off."""
|
||||||
if index is not None or name is not None:
|
if index is not None or name is not None:
|
||||||
if not dev.is_strip:
|
if not dev.children:
|
||||||
echo("Index and name are only for power strips!")
|
echo("Index and name are only for devices with children.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if index is not None:
|
if index is not None:
|
||||||
|
@ -13,6 +13,7 @@ from kasa import (
|
|||||||
DeviceError,
|
DeviceError,
|
||||||
EmeterStatus,
|
EmeterStatus,
|
||||||
KasaException,
|
KasaException,
|
||||||
|
Module,
|
||||||
UnsupportedDeviceError,
|
UnsupportedDeviceError,
|
||||||
)
|
)
|
||||||
from kasa.cli import (
|
from kasa.cli import (
|
||||||
@ -21,11 +22,15 @@ from kasa.cli import (
|
|||||||
brightness,
|
brightness,
|
||||||
cli,
|
cli,
|
||||||
cmd_command,
|
cmd_command,
|
||||||
|
effect,
|
||||||
emeter,
|
emeter,
|
||||||
|
hsv,
|
||||||
|
led,
|
||||||
raw_command,
|
raw_command,
|
||||||
reboot,
|
reboot,
|
||||||
state,
|
state,
|
||||||
sysinfo,
|
sysinfo,
|
||||||
|
temperature,
|
||||||
toggle,
|
toggle,
|
||||||
update_credentials,
|
update_credentials,
|
||||||
wifi,
|
wifi,
|
||||||
@ -34,7 +39,6 @@ from kasa.discover import Discover, DiscoveryResult
|
|||||||
from kasa.iot import IotDevice
|
from kasa.iot import IotDevice
|
||||||
|
|
||||||
from .conftest import (
|
from .conftest import (
|
||||||
device_iot,
|
|
||||||
device_smart,
|
device_smart,
|
||||||
get_device_for_fixture_protocol,
|
get_device_for_fixture_protocol,
|
||||||
handle_turn_on,
|
handle_turn_on,
|
||||||
@ -78,11 +82,10 @@ async def test_update_called_by_cli(dev, mocker, runner):
|
|||||||
update.assert_called()
|
update.assert_called()
|
||||||
|
|
||||||
|
|
||||||
@device_iot
|
async def test_sysinfo(dev: Device, runner):
|
||||||
async def test_sysinfo(dev, runner):
|
|
||||||
res = await runner.invoke(sysinfo, obj=dev)
|
res = await runner.invoke(sysinfo, obj=dev)
|
||||||
assert "System info" in res.output
|
assert "System info" in res.output
|
||||||
assert dev.alias in res.output
|
assert dev.model in res.output
|
||||||
|
|
||||||
|
|
||||||
@turn_on
|
@turn_on
|
||||||
@ -108,7 +111,6 @@ async def test_toggle(dev, turn_on, runner):
|
|||||||
assert dev.is_on != turn_on
|
assert dev.is_on != turn_on
|
||||||
|
|
||||||
|
|
||||||
@device_iot
|
|
||||||
async def test_alias(dev, runner):
|
async def test_alias(dev, runner):
|
||||||
res = await runner.invoke(alias, obj=dev)
|
res = await runner.invoke(alias, obj=dev)
|
||||||
assert f"Alias: {dev.alias}" in res.output
|
assert f"Alias: {dev.alias}" in res.output
|
||||||
@ -308,15 +310,14 @@ async def test_emeter(dev: Device, mocker, runner):
|
|||||||
daily.assert_called_with(year=1900, month=12)
|
daily.assert_called_with(year=1900, month=12)
|
||||||
|
|
||||||
|
|
||||||
@device_iot
|
async def test_brightness(dev: Device, runner):
|
||||||
async def test_brightness(dev, runner):
|
|
||||||
res = await runner.invoke(brightness, obj=dev)
|
res = await runner.invoke(brightness, obj=dev)
|
||||||
if not dev.is_dimmable:
|
if not (light := dev.modules.get(Module.Light)) or not light.is_dimmable:
|
||||||
assert "This device does not support brightness." in res.output
|
assert "This device does not support brightness." in res.output
|
||||||
return
|
return
|
||||||
|
|
||||||
res = await runner.invoke(brightness, obj=dev)
|
res = await runner.invoke(brightness, obj=dev)
|
||||||
assert f"Brightness: {dev.brightness}" in res.output
|
assert f"Brightness: {light.brightness}" in res.output
|
||||||
|
|
||||||
res = await runner.invoke(brightness, ["12"], obj=dev)
|
res = await runner.invoke(brightness, ["12"], obj=dev)
|
||||||
assert "Setting brightness" in res.output
|
assert "Setting brightness" in res.output
|
||||||
@ -326,7 +327,110 @@ async def test_brightness(dev, runner):
|
|||||||
assert "Brightness: 12" in res.output
|
assert "Brightness: 12" in res.output
|
||||||
|
|
||||||
|
|
||||||
@device_iot
|
async def test_color_temperature(dev: Device, runner):
|
||||||
|
res = await runner.invoke(temperature, obj=dev)
|
||||||
|
if not (light := dev.modules.get(Module.Light)) or not light.is_variable_color_temp:
|
||||||
|
assert "Device does not support color temperature" in res.output
|
||||||
|
return
|
||||||
|
|
||||||
|
res = await runner.invoke(temperature, obj=dev)
|
||||||
|
assert f"Color temperature: {light.color_temp}" in res.output
|
||||||
|
valid_range = light.valid_temperature_range
|
||||||
|
assert f"(min: {valid_range.min}, max: {valid_range.max})" in res.output
|
||||||
|
|
||||||
|
val = int((valid_range.min + valid_range.max) / 2)
|
||||||
|
res = await runner.invoke(temperature, [str(val)], obj=dev)
|
||||||
|
assert "Setting color temperature to " in res.output
|
||||||
|
await dev.update()
|
||||||
|
|
||||||
|
res = await runner.invoke(temperature, obj=dev)
|
||||||
|
assert f"Color temperature: {val}" in res.output
|
||||||
|
assert res.exit_code == 0
|
||||||
|
|
||||||
|
invalid_max = valid_range.max + 100
|
||||||
|
# Lights that support the maximum range will not get past the click cli range check
|
||||||
|
# So can't be tested for the internal range check.
|
||||||
|
if invalid_max < 9000:
|
||||||
|
res = await runner.invoke(temperature, [str(invalid_max)], obj=dev)
|
||||||
|
assert res.exit_code == 1
|
||||||
|
assert isinstance(res.exception, ValueError)
|
||||||
|
|
||||||
|
res = await runner.invoke(temperature, [str(9100)], obj=dev)
|
||||||
|
assert res.exit_code == 2
|
||||||
|
|
||||||
|
|
||||||
|
async def test_color_hsv(dev: Device, runner: CliRunner):
|
||||||
|
res = await runner.invoke(hsv, obj=dev)
|
||||||
|
if not (light := dev.modules.get(Module.Light)) or not light.is_color:
|
||||||
|
assert "Device does not support colors" in res.output
|
||||||
|
return
|
||||||
|
|
||||||
|
res = await runner.invoke(hsv, obj=dev)
|
||||||
|
assert f"Current HSV: {light.hsv}" in res.output
|
||||||
|
|
||||||
|
res = await runner.invoke(hsv, ["180", "50", "50"], obj=dev)
|
||||||
|
assert "Setting HSV: 180 50 50" in res.output
|
||||||
|
assert res.exit_code == 0
|
||||||
|
await dev.update()
|
||||||
|
|
||||||
|
res = await runner.invoke(hsv, ["180", "50"], obj=dev)
|
||||||
|
assert "Setting a color requires 3 values." in res.output
|
||||||
|
assert res.exit_code == 2
|
||||||
|
|
||||||
|
|
||||||
|
async def test_light_effect(dev: Device, runner: CliRunner):
|
||||||
|
res = await runner.invoke(effect, obj=dev)
|
||||||
|
if not (light_effect := dev.modules.get(Module.LightEffect)):
|
||||||
|
assert "Device does not support effects" in res.output
|
||||||
|
return
|
||||||
|
|
||||||
|
# Start off with a known state of off
|
||||||
|
await light_effect.set_effect(light_effect.LIGHT_EFFECTS_OFF)
|
||||||
|
await dev.update()
|
||||||
|
assert light_effect.effect == light_effect.LIGHT_EFFECTS_OFF
|
||||||
|
|
||||||
|
res = await runner.invoke(effect, obj=dev)
|
||||||
|
msg = (
|
||||||
|
"Setting an effect requires a named built-in effect: "
|
||||||
|
+ f"{light_effect.effect_list}"
|
||||||
|
)
|
||||||
|
assert msg in res.output
|
||||||
|
assert res.exit_code == 2
|
||||||
|
|
||||||
|
res = await runner.invoke(effect, [light_effect.effect_list[1]], obj=dev)
|
||||||
|
assert f"Setting Effect: {light_effect.effect_list[1]}" in res.output
|
||||||
|
assert res.exit_code == 0
|
||||||
|
await dev.update()
|
||||||
|
assert light_effect.effect == light_effect.effect_list[1]
|
||||||
|
|
||||||
|
res = await runner.invoke(effect, ["foobar"], obj=dev)
|
||||||
|
assert f"Effect must be one of: {light_effect.effect_list}" in res.output
|
||||||
|
assert res.exit_code == 2
|
||||||
|
|
||||||
|
|
||||||
|
async def test_led(dev: Device, runner: CliRunner):
|
||||||
|
res = await runner.invoke(led, obj=dev)
|
||||||
|
if not (led_module := dev.modules.get(Module.Led)):
|
||||||
|
assert "Device does not support led" in res.output
|
||||||
|
return
|
||||||
|
|
||||||
|
res = await runner.invoke(led, obj=dev)
|
||||||
|
assert f"LED state: {led_module.led}" in res.output
|
||||||
|
assert res.exit_code == 0
|
||||||
|
|
||||||
|
res = await runner.invoke(led, ["on"], obj=dev)
|
||||||
|
assert "Turning led to True" in res.output
|
||||||
|
assert res.exit_code == 0
|
||||||
|
await dev.update()
|
||||||
|
assert led_module.led is True
|
||||||
|
|
||||||
|
res = await runner.invoke(led, ["off"], obj=dev)
|
||||||
|
assert "Turning led to False" in res.output
|
||||||
|
assert res.exit_code == 0
|
||||||
|
await dev.update()
|
||||||
|
assert led_module.led is False
|
||||||
|
|
||||||
|
|
||||||
async def test_json_output(dev: Device, mocker, runner):
|
async def test_json_output(dev: Device, mocker, runner):
|
||||||
"""Test that the json output produces correct output."""
|
"""Test that the json output produces correct output."""
|
||||||
mocker.patch("kasa.Discover.discover", return_value={"127.0.0.1": dev})
|
mocker.patch("kasa.Discover.discover", return_value={"127.0.0.1": dev})
|
||||||
@ -375,7 +479,6 @@ async def test_credentials(discovery_mock, mocker, runner):
|
|||||||
assert "Username:foo Password:bar\n" in res.output
|
assert "Username:foo Password:bar\n" in res.output
|
||||||
|
|
||||||
|
|
||||||
@device_iot
|
|
||||||
async def test_without_device_type(dev, mocker, runner):
|
async def test_without_device_type(dev, mocker, runner):
|
||||||
"""Test connecting without the device type."""
|
"""Test connecting without the device type."""
|
||||||
discovery_mock = mocker.patch(
|
discovery_mock = mocker.patch(
|
||||||
|
Loading…
Reference in New Issue
Block a user