Add flake8-pytest-style (PT) for ruff (#1105)

This will catch common issues with pytest code.

* Use `match` when using `pytest.raises()` for base exception types like
`TypeError` or `ValueError`
* Use tuples for `parametrize()`
* Enforces `pytest.raises()` to contain simple statements, using `noqa`
to skip this on some cases for now.
* Fixes incorrect exception type (valueerror instead of typeerror) for
iotdimmer.
* Adds check valid types for `iotbulb.set_hsv` and `color` smart module.
* Consolidate exception messages for common interface modules.
This commit is contained in:
Teemu R. 2024-08-30 17:30:07 +02:00 committed by GitHub
parent 3e43781bb2
commit 6a86ffbbba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 248 additions and 150 deletions

View File

@ -388,10 +388,14 @@ class IotBulb(IotDevice):
if not self._is_color: if not self._is_color:
raise KasaException("Bulb does not support color.") raise KasaException("Bulb does not support color.")
if not isinstance(hue, int) or not (0 <= hue <= 360): if not isinstance(hue, int):
raise TypeError("Hue must be an integer.")
if not (0 <= hue <= 360):
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)") raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
if not isinstance(saturation, int) or not (0 <= saturation <= 100): if not isinstance(saturation, int):
raise TypeError("Saturation must be an integer.")
if not (0 <= saturation <= 100):
raise ValueError( raise ValueError(
f"Invalid saturation value: {saturation} (valid range: 0-100%)" f"Invalid saturation value: {saturation} (valid range: 0-100%)"
) )
@ -445,7 +449,9 @@ class IotBulb(IotDevice):
return await self._set_light_state(light_state, transition=transition) return await self._set_light_state(light_state, transition=transition)
def _raise_for_invalid_brightness(self, value): def _raise_for_invalid_brightness(self, value):
if not isinstance(value, int) or not (0 <= value <= 100): if not isinstance(value, int):
raise TypeError("Brightness must be an integer")
if not (0 <= value <= 100):
raise ValueError(f"Invalid brightness value: {value} (valid range: 0-100%)") raise ValueError(f"Invalid brightness value: {value} (valid range: 0-100%)")
@property # type: ignore @property # type: ignore

View File

@ -118,7 +118,9 @@ class IotDimmer(IotPlug):
) )
if not 0 <= brightness <= 100: if not 0 <= brightness <= 100:
raise ValueError("Brightness value %s is not valid." % brightness) raise ValueError(
f"Invalid brightness value: {brightness} (valid range: 0-100%)"
)
# Dimmers do not support a brightness of 0, but bulbs do. # Dimmers do not support a brightness of 0, but bulbs do.
# Coerce 0 to 1 to maintain the same interface between dimmers and bulbs. # Coerce 0 to 1 to maintain the same interface between dimmers and bulbs.
@ -161,20 +163,18 @@ class IotDimmer(IotPlug):
A brightness value of 0 will turn off the dimmer. A brightness value of 0 will turn off the dimmer.
""" """
if not isinstance(brightness, int): if not isinstance(brightness, int):
raise ValueError( raise TypeError(f"Brightness must be an integer, not {type(brightness)}.")
"Brightness must be integer, " "not of %s.", type(brightness)
)
if not 0 <= brightness <= 100: if not 0 <= brightness <= 100:
raise ValueError("Brightness value %s is not valid." % brightness) raise ValueError(
f"Invalid brightness value: {brightness} (valid range: 0-100%)"
)
# If zero set to 1 millisecond # If zero set to 1 millisecond
if transition == 0: if transition == 0:
transition = 1 transition = 1
if not isinstance(transition, int): if not isinstance(transition, int):
raise ValueError( raise TypeError(f"Transition must be integer, not of {type(transition)}.")
"Transition must be integer, " "not of %s.", type(transition)
)
if transition <= 0: if transition <= 0:
raise ValueError("Transition value %s is not valid." % transition) raise ValueError("Transition value %s is not valid." % transition)

View File

@ -51,10 +51,12 @@ class Color(SmartModule):
return HSV(hue=h, saturation=s, value=v) return HSV(hue=h, saturation=s, value=v)
def _raise_for_invalid_brightness(self, value: int): def _raise_for_invalid_brightness(self, value):
"""Raise error on invalid brightness value.""" """Raise error on invalid brightness value."""
if not isinstance(value, int) or not (1 <= value <= 100): if not isinstance(value, int):
raise ValueError(f"Invalid brightness value: {value} (valid range: 1-100%)") raise TypeError("Brightness must be an integer")
if not (0 <= value <= 100):
raise ValueError(f"Invalid brightness value: {value} (valid range: 0-100%)")
async def set_hsv( async def set_hsv(
self, self,
@ -73,10 +75,14 @@ class Color(SmartModule):
:param int value: value in percentage [0, 100] :param int value: value in percentage [0, 100]
:param int transition: transition in milliseconds. :param int transition: transition in milliseconds.
""" """
if not isinstance(hue, int) or not (0 <= hue <= 360): if not isinstance(hue, int):
raise TypeError("Hue must be an integer")
if not (0 <= hue <= 360):
raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)") raise ValueError(f"Invalid hue value: {hue} (valid range: 0-360)")
if not isinstance(saturation, int) or not (0 <= saturation <= 100): if not isinstance(saturation, int):
raise TypeError("Saturation must be an integer")
if not (0 <= saturation <= 100):
raise ValueError( raise ValueError(
f"Invalid saturation value: {saturation} (valid range: 0-100%)" f"Invalid saturation value: {saturation} (valid range: 0-100%)"
) )

View File

@ -90,7 +90,7 @@ class LightEffect(SmartModule, SmartLightEffect):
""" """
if effect != self.LIGHT_EFFECTS_OFF and effect not in self._scenes_names_to_id: if effect != self.LIGHT_EFFECTS_OFF and effect not in self._scenes_names_to_id:
raise ValueError( raise ValueError(
f"Cannot set light effect to {effect}, possible values " f"The effect {effect} is not a built in effect. Possible values "
f"are: {self.LIGHT_EFFECTS_OFF} " f"are: {self.LIGHT_EFFECTS_OFF} "
f"{' '.join(self._scenes_names_to_id.keys())}" f"{' '.join(self._scenes_names_to_id.keys())}"
) )

View File

@ -25,7 +25,7 @@ async def handle_turn_on(dev, turn_on):
await dev.turn_off() await dev.turn_off()
@pytest.fixture @pytest.fixture()
def dummy_protocol(): def dummy_protocol():
"""Return a smart protocol instance with a mocking-ready dummy transport.""" """Return a smart protocol instance with a mocking-ready dummy transport."""

View File

@ -75,7 +75,7 @@ new_discovery = parametrize_discovery(
async def discovery_mock(request, mocker): async def discovery_mock(request, mocker):
"""Mock discovery and patch protocol queries to use Fake protocols.""" """Mock discovery and patch protocol queries to use Fake protocols."""
fixture_info: FixtureInfo = request.param fixture_info: FixtureInfo = request.param
yield patch_discovery({DISCOVERY_MOCK_IP: fixture_info}, mocker) return patch_discovery({DISCOVERY_MOCK_IP: fixture_info}, mocker)
def create_discovery_mock(ip: str, fixture_data: dict): def create_discovery_mock(ip: str, fixture_data: dict):
@ -253,4 +253,4 @@ def unsupported_device_info(request, mocker):
mocker.patch("kasa.discover._DiscoverProtocol.do_discover", mock_discover) mocker.patch("kasa.discover._DiscoverProtocol.do_discover", mock_discover)
yield discovery_data return discovery_data

View File

@ -18,17 +18,18 @@ async def test_brightness_component(dev: SmartDevice):
# Test getting the value # Test getting the value
feature = brightness._device.features["brightness"] feature = brightness._device.features["brightness"]
assert isinstance(feature.value, int) assert isinstance(feature.value, int)
assert feature.value > 1 and feature.value <= 100 assert feature.value > 1
assert feature.value <= 100
# Test setting the value # Test setting the value
await feature.set_value(10) await feature.set_value(10)
await dev.update() await dev.update()
assert feature.value == 10 assert feature.value == 10
with pytest.raises(ValueError): with pytest.raises(ValueError, match="out of range"):
await feature.set_value(feature.minimum_value - 10) await feature.set_value(feature.minimum_value - 10)
with pytest.raises(ValueError): with pytest.raises(ValueError, match="out of range"):
await feature.set_value(feature.maximum_value + 10) await feature.set_value(feature.maximum_value + 10)
@ -41,15 +42,16 @@ async def test_brightness_dimmable(dev: IotDevice):
# Test getting the value # Test getting the value
feature = dev.features["brightness"] feature = dev.features["brightness"]
assert isinstance(feature.value, int) assert isinstance(feature.value, int)
assert feature.value > 0 and feature.value <= 100 assert feature.value > 0
assert feature.value <= 100
# Test setting the value # Test setting the value
await feature.set_value(10) await feature.set_value(10)
await dev.update() await dev.update()
assert feature.value == 10 assert feature.value == 10
with pytest.raises(ValueError): with pytest.raises(ValueError, match="out of range"):
await feature.set_value(feature.minimum_value - 10) await feature.set_value(feature.minimum_value - 10)
with pytest.raises(ValueError): with pytest.raises(ValueError, match="out of range"):
await feature.set_value(feature.maximum_value + 10) await feature.set_value(feature.maximum_value + 10)

View File

@ -23,8 +23,8 @@ async def test_colortemp_component(dev: SmartDevice):
await dev.update() await dev.update()
assert feature.value == new_value assert feature.value == new_value
with pytest.raises(ValueError): with pytest.raises(ValueError, match="out of range"):
await feature.set_value(feature.minimum_value - 10) await feature.set_value(feature.minimum_value - 10)
with pytest.raises(ValueError): with pytest.raises(ValueError, match="out of range"):
await feature.set_value(feature.maximum_value + 10) await feature.set_value(feature.maximum_value + 10)

View File

@ -18,7 +18,7 @@ autooff = parametrize(
@autooff @autooff
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, prop_name, type", ("feature", "prop_name", "type"),
[ [
("auto_off_enabled", "enabled", bool), ("auto_off_enabled", "enabled", bool),
("auto_off_minutes", "delay", int), ("auto_off_minutes", "delay", int),

View File

@ -10,7 +10,7 @@ contact = parametrize(
@contact @contact
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, type", ("feature", "type"),
[ [
("is_open", bool), ("is_open", bool),
], ],

View File

@ -76,8 +76,8 @@ async def test_fan_module(dev: SmartDevice, mocker: MockerFixture):
await dev.update() await dev.update()
assert not device.is_on assert not device.is_on
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Invalid level"):
await fan.set_fan_speed_level(-1) await fan.set_fan_speed_level(-1)
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Invalid level"):
await fan.set_fan_speed_level(5) await fan.set_fan_speed_level(5)

View File

@ -19,11 +19,10 @@ firmware = parametrize(
@firmware @firmware
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, prop_name, type, required_version", ("feature", "prop_name", "type", "required_version"),
[ [
("auto_update_enabled", "auto_update_enabled", bool, 2), ("auto_update_enabled", "auto_update_enabled", bool, 2),
("update_available", "update_available", bool, 1), ("update_available", "update_available", bool, 1),
("update_available", "update_available", bool, 1),
("current_firmware_version", "current_firmware", str, 1), ("current_firmware_version", "current_firmware", str, 1),
("available_firmware_version", "latest_firmware", str, 1), ("available_firmware_version", "latest_firmware", str, 1),
], ],

View File

@ -10,7 +10,7 @@ humidity = parametrize(
@humidity @humidity
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, type", ("feature", "type"),
[ [
("humidity", int), ("humidity", int),
("humidity_warning", bool), ("humidity_warning", bool),

View File

@ -37,7 +37,7 @@ async def test_light_effect(dev: Device, mocker: MockerFixture):
assert light_effect.effect == effect assert light_effect.effect == effect
assert feature.value == effect assert feature.value == effect
with pytest.raises(ValueError): with pytest.raises(ValueError, match="The effect foobar is not a built in effect"):
await light_effect.set_effect("foobar") await light_effect.set_effect("foobar")

View File

@ -54,7 +54,7 @@ async def test_light_strip_effect(dev: Device, mocker: MockerFixture):
assert light_effect.effect == effect assert light_effect.effect == effect
assert feature.value == effect assert feature.value == effect
with pytest.raises(ValueError): with pytest.raises(ValueError, match="The effect foobar is not a built in effect"):
await light_effect.set_effect("foobar") await light_effect.set_effect("foobar")

View File

@ -10,7 +10,7 @@ motion = parametrize(
@motion @motion
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, type", ("feature", "type"),
[ [
("motion_detected", bool), ("motion_detected", bool),
], ],

View File

@ -16,7 +16,7 @@ temperature_warning = parametrize(
@temperature @temperature
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, type", ("feature", "type"),
[ [
("temperature", float), ("temperature", float),
("temperature_unit", str), ("temperature_unit", str),

View File

@ -1,4 +1,5 @@
import logging import logging
import re
import pytest import pytest
@ -15,7 +16,7 @@ temperature = parametrize(
@thermostats_smart @thermostats_smart
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, type", ("feature", "type"),
[ [
("target_temperature", float), ("target_temperature", float),
("temperature_offset", int), ("temperature_offset", int),
@ -59,10 +60,14 @@ async def test_set_temperature_invalid_values(dev):
"""Test that out-of-bounds temperature values raise errors.""" """Test that out-of-bounds temperature values raise errors."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module: TemperatureControl = dev.modules["TemperatureControl"]
with pytest.raises(ValueError): with pytest.raises(
ValueError, match="Invalid target temperature -1, must be in range"
):
await temp_module.set_target_temperature(-1) await temp_module.set_target_temperature(-1)
with pytest.raises(ValueError): with pytest.raises(
ValueError, match="Invalid target temperature 100, must be in range"
):
await temp_module.set_target_temperature(100) await temp_module.set_target_temperature(100)
@ -70,10 +75,14 @@ async def test_set_temperature_invalid_values(dev):
async def test_temperature_offset(dev): async def test_temperature_offset(dev):
"""Test the temperature offset API.""" """Test the temperature offset API."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"] temp_module: TemperatureControl = dev.modules["TemperatureControl"]
with pytest.raises(ValueError): with pytest.raises(
ValueError, match=re.escape("Temperature offset must be [-10, 10]")
):
await temp_module.set_temperature_offset(100) await temp_module.set_temperature_offset(100)
with pytest.raises(ValueError): with pytest.raises(
ValueError, match=re.escape("Temperature offset must be [-10, 10]")
):
await temp_module.set_temperature_offset(-100) await temp_module.set_temperature_offset(-100)
await temp_module.set_temperature_offset(5) await temp_module.set_temperature_offset(5)
@ -83,7 +92,7 @@ async def test_temperature_offset(dev):
@thermostats_smart @thermostats_smart
@pytest.mark.parametrize( @pytest.mark.parametrize(
"mode, states, frost_protection", ("mode", "states", "frost_protection"),
[ [
pytest.param(ThermostatState.Idle, [], False, id="idle has empty"), pytest.param(ThermostatState.Idle, [], False, id="idle has empty"),
pytest.param( pytest.param(
@ -114,7 +123,7 @@ async def test_thermostat_mode(dev, mode, states, frost_protection):
@thermostats_smart @thermostats_smart
@pytest.mark.parametrize( @pytest.mark.parametrize(
"mode, states, msg", ("mode", "states", "msg"),
[ [
pytest.param( pytest.param(
ThermostatState.Heating, ThermostatState.Heating,

View File

@ -12,7 +12,7 @@ waterleak = parametrize(
@waterleak @waterleak
@pytest.mark.parametrize( @pytest.mark.parametrize(
"feature, prop_name, type", ("feature", "prop_name", "type"),
[ [
("water_alert", "alert", int), ("water_alert", "alert", int),
("water_leak", "status", Enum), ("water_leak", "status", Enum),

View File

@ -100,7 +100,7 @@ async def test_login(mocker, status_code, error_code, inner_error_code, expectat
@pytest.mark.parametrize( @pytest.mark.parametrize(
"inner_error_codes, expectation, call_count", ("inner_error_codes", "expectation", "call_count"),
[ [
([SmartErrorCode.LOGIN_ERROR, 0, 0, 0], does_not_raise(), 4), ([SmartErrorCode.LOGIN_ERROR, 0, 0, 0], does_not_raise(), 4),
( (
@ -298,7 +298,7 @@ async def test_unknown_errors(mocker, error_code):
"requestID": 1, "requestID": 1,
"terminal_uuid": "foobar", "terminal_uuid": "foobar",
} }
with pytest.raises(KasaException): with pytest.raises(KasaException): # noqa: PT012
res = await transport.send(json_dumps(request)) res = await transport.send(json_dumps(request))
assert res is SmartErrorCode.INTERNAL_UNKNOWN_ERROR assert res is SmartErrorCode.INTERNAL_UNKNOWN_ERROR
@ -315,7 +315,7 @@ async def test_port_override():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"device_delay_required, should_error, should_succeed", ("device_delay_required", "should_error", "should_succeed"),
[ [
pytest.param(0, False, True, id="No error"), pytest.param(0, False, True, id="No error"),
pytest.param(0.125, True, True, id="Error then succeed"), pytest.param(0.125, True, True, id="Error then succeed"),

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
import re
import pytest import pytest
from voluptuous import ( from voluptuous import (
All, All,
@ -51,10 +53,8 @@ async def test_state_attributes(dev: Device):
@bulb_iot @bulb_iot
async def test_light_state_without_update(dev: IotBulb, monkeypatch): async def test_light_state_without_update(dev: IotBulb, monkeypatch):
monkeypatch.setitem(dev._last_update["system"]["get_sysinfo"], "light_state", None)
with pytest.raises(KasaException): with pytest.raises(KasaException):
monkeypatch.setitem(
dev._last_update["system"]["get_sysinfo"], "light_state", None
)
print(dev.light_state) print(dev.light_state)
@ -114,23 +114,72 @@ async def test_light_set_state(dev: IotBulb, mocker):
@color_bulb @color_bulb
@turn_on @turn_on
async def test_invalid_hsv(dev: Device, turn_on): @pytest.mark.parametrize(
("hue", "sat", "brightness", "exception_cls", "error"),
[
pytest.param(-1, 0, 0, ValueError, "Invalid hue", id="hue out of range"),
pytest.param(361, 0, 0, ValueError, "Invalid hue", id="hue out of range"),
pytest.param(
0.5, 0, 0, TypeError, "Hue must be an integer", id="hue invalid type"
),
pytest.param(
"foo", 0, 0, TypeError, "Hue must be an integer", id="hue invalid type"
),
pytest.param(
0, -1, 0, ValueError, "Invalid saturation", id="saturation out of range"
),
pytest.param(
0, 101, 0, ValueError, "Invalid saturation", id="saturation out of range"
),
pytest.param(
0,
0.5,
0,
TypeError,
"Saturation must be an integer",
id="saturation invalid type",
),
pytest.param(
0,
"foo",
0,
TypeError,
"Saturation must be an integer",
id="saturation invalid type",
),
pytest.param(
0, 0, -1, ValueError, "Invalid brightness", id="brightness out of range"
),
pytest.param(
0, 0, 101, ValueError, "Invalid brightness", id="brightness out of range"
),
pytest.param(
0,
0,
0.5,
TypeError,
"Brightness must be an integer",
id="brightness invalid type",
),
pytest.param(
0,
0,
"foo",
TypeError,
"Brightness must be an integer",
id="brightness invalid type",
),
],
)
async def test_invalid_hsv(
dev: Device, turn_on, hue, sat, brightness, exception_cls, error
):
light = dev.modules.get(Module.Light) light = dev.modules.get(Module.Light)
assert light assert light
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
assert light.is_color assert light.is_color
with pytest.raises(exception_cls, match=error):
for invalid_hue in [-1, 361, 0.5]: await light.set_hsv(hue, sat, brightness)
with pytest.raises(ValueError):
await light.set_hsv(invalid_hue, 0, 0) # type: ignore[arg-type]
for invalid_saturation in [-1, 101, 0.5]:
with pytest.raises(ValueError):
await light.set_hsv(0, invalid_saturation, 0) # type: ignore[arg-type]
for invalid_brightness in [-1, 101, 0.5]:
with pytest.raises(ValueError):
await light.set_hsv(0, 0, invalid_brightness) # type: ignore[arg-type]
@color_bulb @color_bulb
@ -201,9 +250,13 @@ async def test_smart_temp_range(dev: Device):
async def test_out_of_range_temperature(dev: Device): async def test_out_of_range_temperature(dev: Device):
light = dev.modules.get(Module.Light) light = dev.modules.get(Module.Light)
assert light assert light
with pytest.raises(ValueError): with pytest.raises(
ValueError, match="Temperature should be between \d+ and \d+, was 1000"
):
await light.set_color_temp(1000) await light.set_color_temp(1000)
with pytest.raises(ValueError): with pytest.raises(
ValueError, match="Temperature should be between \d+ and \d+, was 10000"
):
await light.set_color_temp(10000) await light.set_color_temp(10000)
@ -236,7 +289,7 @@ async def test_dimmable_brightness(dev: IotBulb, turn_on):
await dev.update() await dev.update()
assert dev.brightness == 10 assert dev.brightness == 10
with pytest.raises(ValueError): with pytest.raises(TypeError, match="Brightness must be an integer"):
await dev.set_brightness("foo") # type: ignore[arg-type] await dev.set_brightness("foo") # type: ignore[arg-type]
@ -264,10 +317,16 @@ async def test_dimmable_brightness_transition(dev: IotBulb, mocker):
async def test_invalid_brightness(dev: IotBulb): async def test_invalid_brightness(dev: IotBulb):
assert dev._is_dimmable assert dev._is_dimmable
with pytest.raises(ValueError): with pytest.raises(
ValueError,
match=re.escape("Invalid brightness value: 110 (valid range: 0-100%)"),
):
await dev.set_brightness(110) await dev.set_brightness(110)
with pytest.raises(ValueError): with pytest.raises(
ValueError,
match=re.escape("Invalid brightness value: -100 (valid range: 0-100%)"),
):
await dev.set_brightness(-100) await dev.set_brightness(-100)

View File

@ -128,9 +128,9 @@ async def test_light_effect_module(dev: Device, mocker: MockerFixture):
assert feat.value == second_effect assert feat.value == second_effect
call.reset_mock() call.reset_mock()
with pytest.raises(ValueError): with pytest.raises(ValueError, match="The effect foobar is not a built in effect."):
await light_effect_module.set_effect("foobar") await light_effect_module.set_effect("foobar")
call.assert_not_called() call.assert_not_called()
@light_effect @light_effect
@ -174,10 +174,10 @@ async def test_light_brightness(dev: Device):
await dev.update() await dev.update()
assert light.brightness == 10 assert light.brightness == 10
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Invalid brightness value: "):
await light.set_brightness(feature.minimum_value - 10) await light.set_brightness(feature.minimum_value - 10)
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Invalid brightness value: "):
await light.set_brightness(feature.maximum_value + 10) await light.set_brightness(feature.maximum_value + 10)
@ -213,10 +213,10 @@ async def test_light_color_temp(dev: Device):
assert light.color_temp == feature.minimum_value + 20 assert light.color_temp == feature.minimum_value + 20
assert light.brightness == 60 assert light.brightness == 60
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Temperature should be between \d+ and \d+"):
await light.set_color_temp(feature.minimum_value - 10) await light.set_color_temp(feature.minimum_value - 10)
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Temperature should be between \d+ and \d+"):
await light.set_color_temp(feature.maximum_value + 10) await light.set_color_temp(feature.maximum_value + 10)
@ -293,9 +293,9 @@ async def test_light_preset_module(dev: Device, mocker: MockerFixture):
assert preset_mod.preset == second_preset assert preset_mod.preset == second_preset
assert feat.value == second_preset assert feat.value == second_preset
with pytest.raises(ValueError): with pytest.raises(ValueError, match="foobar is not a valid preset"):
await preset_mod.set_preset("foobar") await preset_mod.set_preset("foobar")
assert call.call_count == 3 assert call.call_count == 3
@light_preset @light_preset
@ -315,9 +315,7 @@ async def test_light_preset_save(dev: Device, mocker: MockerFixture):
await preset_mod.save_preset(second_preset, new_preset) await preset_mod.save_preset(second_preset, new_preset)
await dev.update() await dev.update()
new_preset_state = preset_mod.preset_states_list[0] new_preset_state = preset_mod.preset_states_list[0]
assert ( assert new_preset_state.brightness == new_preset.brightness
new_preset_state.brightness == new_preset.brightness assert new_preset_state.hue == new_preset.hue
and new_preset_state.hue == new_preset.hue assert new_preset_state.saturation == new_preset.saturation
and new_preset_state.saturation == new_preset.saturation assert new_preset_state.color_temp == new_preset.color_temp
and new_preset_state.color_temp == new_preset.color_temp
)

View File

@ -103,7 +103,7 @@ async def test_create_thin_wrapper():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"device_class, use_class", kasa.deprecated_smart_devices.items() ("device_class", "use_class"), kasa.deprecated_smart_devices.items()
) )
def test_deprecated_devices(device_class, use_class): def test_deprecated_devices(device_class, use_class):
package_name = ".".join(use_class.__module__.split(".")[:-1]) package_name = ".".join(use_class.__module__.split(".")[:-1])
@ -117,7 +117,9 @@ def test_deprecated_devices(device_class, use_class):
getattr(module, use_class.__name__) getattr(module, use_class.__name__)
@pytest.mark.parametrize("deprecated_class, use_class", kasa.deprecated_classes.items()) @pytest.mark.parametrize(
("deprecated_class", "use_class"), kasa.deprecated_classes.items()
)
def test_deprecated_classes(deprecated_class, use_class): def test_deprecated_classes(deprecated_class, use_class):
msg = f"{deprecated_class} is deprecated, use {use_class.__name__} instead" msg = f"{deprecated_class} is deprecated, use {use_class.__name__} instead"
with pytest.deprecated_call(match=msg): with pytest.deprecated_call(match=msg):

View File

@ -46,13 +46,23 @@ async def test_set_brightness_transition(dev, turn_on, mocker):
@dimmer_iot @dimmer_iot
async def test_set_brightness_invalid(dev): async def test_set_brightness_invalid(dev):
for invalid_brightness in [-1, 101, 0.5]: for invalid_brightness in [-1, 101]:
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Invalid brightness"):
await dev.set_brightness(invalid_brightness) await dev.set_brightness(invalid_brightness)
for invalid_transition in [-1, 0.5]: for invalid_type in [0.5, "foo"]:
with pytest.raises(ValueError): with pytest.raises(TypeError, match="Brightness must be an integer"):
await dev.set_brightness(invalid_type)
@dimmer_iot
async def test_set_brightness_invalid_transition(dev):
for invalid_transition in [-1]:
with pytest.raises(ValueError, match="Transition value .+? is not valid."):
await dev.set_brightness(1, transition=invalid_transition) await dev.set_brightness(1, transition=invalid_transition)
for invalid_type in [0.5, "foo"]:
with pytest.raises(TypeError, match="Transition must be integer"):
await dev.set_brightness(1, transition=invalid_type)
@dimmer_iot @dimmer_iot
@ -128,14 +138,24 @@ async def test_set_dimmer_transition_to_off(dev, turn_on, mocker):
@dimmer_iot @dimmer_iot
async def test_set_dimmer_transition_invalid(dev): async def test_set_dimmer_transition_invalid_brightness(dev):
for invalid_brightness in [-1, 101, 0.5]: for invalid_brightness in [-1, 101]:
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Invalid brightness value: "):
await dev.set_dimmer_transition(invalid_brightness, 1000) await dev.set_dimmer_transition(invalid_brightness, 1000)
for invalid_transition in [-1, 0.5]: for invalid_type in [0.5, "foo"]:
with pytest.raises(ValueError): with pytest.raises(TypeError, match="Transition must be integer"):
await dev.set_dimmer_transition(1, invalid_transition) await dev.set_dimmer_transition(1, invalid_type)
@dimmer_iot
async def test_set_dimmer_transition_invalid_transition(dev):
for invalid_transition in [-1]:
with pytest.raises(ValueError, match="Transition value .+? is not valid."):
await dev.set_dimmer_transition(1, transition=invalid_transition)
for invalid_type in [0.5, "foo"]:
with pytest.raises(TypeError, match="Transition must be integer"):
await dev.set_dimmer_transition(1, transition=invalid_type)
@dimmer_iot @dimmer_iot

View File

@ -252,7 +252,7 @@ INVALIDS = [
] ]
@pytest.mark.parametrize("msg, data", INVALIDS) @pytest.mark.parametrize(("msg", "data"), INVALIDS)
async def test_discover_invalid_info(msg, data, mocker): async def test_discover_invalid_info(msg, data, mocker):
"""Make sure that invalid discovery information raises an exception.""" """Make sure that invalid discovery information raises an exception."""
host = "127.0.0.1" host = "127.0.0.1"
@ -304,7 +304,7 @@ async def test_discover_datagram_received(mocker, discovery_data):
assert dev.host == addr assert dev.host == addr
@pytest.mark.parametrize("msg, data", INVALIDS) @pytest.mark.parametrize(("msg", "data"), INVALIDS)
async def test_discover_invalid_responses(msg, data, mocker): async def test_discover_invalid_responses(msg, data, mocker):
"""Verify that we don't crash whole discovery if some devices in the network are sending unexpected data.""" """Verify that we don't crash whole discovery if some devices in the network are sending unexpected data."""
proto = _DiscoverProtocol() proto = _DiscoverProtocol()
@ -349,7 +349,7 @@ async def test_discover_single_authentication(discovery_mock, mocker):
side_effect=AuthenticationError("Failed to authenticate"), side_effect=AuthenticationError("Failed to authenticate"),
) )
with pytest.raises( with pytest.raises( # noqa: PT012
AuthenticationError, AuthenticationError,
match="Failed to authenticate", match="Failed to authenticate",
): ):
@ -495,7 +495,7 @@ async def test_do_discover_drop_packets(mocker, port, do_not_reply_count):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"port, will_timeout", ("port", "will_timeout"),
[(FakeDatagramTransport.GHOST_PORT, True), (20002, False)], [(FakeDatagramTransport.GHOST_PORT, True), (20002, False)],
ids=["unknownport", "unsupporteddevice"], ids=["unknownport", "unsupporteddevice"],
) )

View File

@ -61,7 +61,7 @@ async def test_get_emeter_realtime(dev):
@has_emeter_iot @has_emeter_iot
@pytest.mark.requires_dummy @pytest.mark.requires_dummy()
async def test_get_emeter_daily(dev): async def test_get_emeter_daily(dev):
assert dev.has_emeter assert dev.has_emeter
@ -81,7 +81,7 @@ async def test_get_emeter_daily(dev):
@has_emeter_iot @has_emeter_iot
@pytest.mark.requires_dummy @pytest.mark.requires_dummy()
async def test_get_emeter_monthly(dev): async def test_get_emeter_monthly(dev):
assert dev.has_emeter assert dev.has_emeter

View File

@ -14,7 +14,7 @@ class DummyDevice:
pass pass
@pytest.fixture @pytest.fixture()
def dummy_feature() -> Feature: def dummy_feature() -> Feature:
# create_autospec for device slows tests way too much, so we use a dummy here # create_autospec for device slows tests way too much, so we use a dummy here
@ -49,7 +49,7 @@ def test_feature_api(dummy_feature: Feature):
) )
def test_feature_setter_on_sensor(read_only_type): def test_feature_setter_on_sensor(read_only_type):
"""Test that creating a sensor feature with a setter causes an error.""" """Test that creating a sensor feature with a setter causes an error."""
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Invalid type for configurable feature"):
Feature( Feature(
device=DummyDevice(), # type: ignore[arg-type] device=DummyDevice(), # type: ignore[arg-type]
id="dummy_error", id="dummy_error",
@ -103,7 +103,7 @@ async def test_feature_setter(dev, mocker, dummy_feature: Feature):
async def test_feature_setter_read_only(dummy_feature): async def test_feature_setter_read_only(dummy_feature):
"""Verify that read-only feature raises an exception when trying to change it.""" """Verify that read-only feature raises an exception when trying to change it."""
dummy_feature.attribute_setter = None dummy_feature.attribute_setter = None
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Tried to set read-only feature"):
await dummy_feature.set_value("value for read only feature") await dummy_feature.set_value("value for read only feature")
@ -134,7 +134,7 @@ async def test_feature_choice_list(dummy_feature, caplog, mocker: MockerFixture)
mock_setter.assert_called_with("first") mock_setter.assert_called_with("first")
mock_setter.reset_mock() mock_setter.reset_mock()
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Unexpected value for dummy_feature: invalid"): # noqa: PT012
await dummy_feature.set_value("invalid") await dummy_feature.set_value("invalid")
assert "Unexpected value" in caplog.text assert "Unexpected value" in caplog.text

View File

@ -14,7 +14,7 @@ from ..httpclient import HttpClient
@pytest.mark.parametrize( @pytest.mark.parametrize(
"error, error_raises, error_message", ("error", "error_raises", "error_message"),
[ [
( (
aiohttp.ServerDisconnectedError(), aiohttp.ServerDisconnectedError(),
@ -52,7 +52,7 @@ from ..httpclient import HttpClient
"ServerFingerprintMismatch", "ServerFingerprintMismatch",
), ),
) )
@pytest.mark.parametrize("mock_read", (False, True), ids=("post", "read")) @pytest.mark.parametrize("mock_read", [False, True], ids=("post", "read"))
async def test_httpclient_errors(mocker, error, error_raises, error_message, mock_read): async def test_httpclient_errors(mocker, error, error_raises, error_message, mock_read):
class _mock_response: class _mock_response:
def __init__(self, status, error): def __init__(self, status, error):

View File

@ -89,7 +89,7 @@ async def test_state_info(dev):
assert isinstance(dev.state_information, dict) assert isinstance(dev.state_information, dict)
@pytest.mark.requires_dummy @pytest.mark.requires_dummy()
@device_iot @device_iot
async def test_invalid_connection(mocker, dev): async def test_invalid_connection(mocker, dev):
with ( with (

View File

@ -49,7 +49,7 @@ class _mock_response:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"error, retry_expectation", ("error", "retry_expectation"),
[ [
(Exception("dummy exception"), False), (Exception("dummy exception"), False),
(aiohttp.ServerTimeoutError("dummy exception"), True), (aiohttp.ServerTimeoutError("dummy exception"), True),
@ -79,7 +79,7 @@ async def test_protocol_retries_via_client_session(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"error, retry_expectation", ("error", "retry_expectation"),
[ [
(KasaException("dummy exception"), False), (KasaException("dummy exception"), False),
(_RetryableError("dummy exception"), True), (_RetryableError("dummy exception"), True),
@ -305,7 +305,7 @@ async def test_transport_decrypt_error(mocker, caplog):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"device_credentials, expectation", ("device_credentials", "expectation"),
[ [
(Credentials("foo", "bar"), does_not_raise()), (Credentials("foo", "bar"), does_not_raise()),
(Credentials(), does_not_raise()), (Credentials(), does_not_raise()),
@ -321,7 +321,7 @@ async def test_transport_decrypt_error(mocker, caplog):
ids=("client", "blank", "kasa_setup", "shouldfail"), ids=("client", "blank", "kasa_setup", "shouldfail"),
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
"transport_class, seed_auth_hash_calc", ("transport_class", "seed_auth_hash_calc"),
[ [
pytest.param(KlapTransport, lambda c, s, a: c + a, id="KLAP"), pytest.param(KlapTransport, lambda c, s, a: c + a, id="KLAP"),
pytest.param(KlapTransportV2, lambda c, s, a: c + s + a, id="KLAPV2"), pytest.param(KlapTransportV2, lambda c, s, a: c + s + a, id="KLAPV2"),
@ -365,7 +365,7 @@ async def test_handshake1(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"transport_class, seed_auth_hash_calc1, seed_auth_hash_calc2", ("transport_class", "seed_auth_hash_calc1", "seed_auth_hash_calc2"),
[ [
pytest.param( pytest.param(
KlapTransport, lambda c, s, a: c + a, lambda c, s, a: s + a, id="KLAP" KlapTransport, lambda c, s, a: c + a, lambda c, s, a: s + a, id="KLAP"
@ -466,7 +466,7 @@ async def test_query(mocker):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"response_status, credentials_match, expectation", ("response_status", "credentials_match", "expectation"),
[ [
pytest.param( pytest.param(
(403, 403, 403), (403, 403, 403),

View File

@ -22,7 +22,9 @@ async def test_lightstrip_effect(dev: IotLightStrip):
@lightstrip_iot @lightstrip_iot
async def test_effects_lightstrip_set_effect(dev: IotLightStrip): async def test_effects_lightstrip_set_effect(dev: IotLightStrip):
with pytest.raises(ValueError): with pytest.raises(
ValueError, match="The effect Not real is not a built in effect"
):
await dev.set_effect("Not real") await dev.set_effect("Not real")
await dev.set_effect("Candy Cane") await dev.set_effect("Candy Cane")

View File

@ -33,7 +33,7 @@ from .fakeprotocol_iot import FakeIotTransport
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class", ("protocol_class", "transport_class"),
[ [
(_deprecated_TPLinkSmartHomeProtocol, XorTransport), (_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport), (IotProtocol, XorTransport),
@ -63,7 +63,7 @@ async def test_protocol_retries(mocker, retry_count, protocol_class, transport_c
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class", ("protocol_class", "transport_class"),
[ [
(_deprecated_TPLinkSmartHomeProtocol, XorTransport), (_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport), (IotProtocol, XorTransport),
@ -87,7 +87,7 @@ async def test_protocol_no_retry_on_unreachable(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class", ("protocol_class", "transport_class"),
[ [
(_deprecated_TPLinkSmartHomeProtocol, XorTransport), (_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport), (IotProtocol, XorTransport),
@ -111,7 +111,7 @@ async def test_protocol_no_retry_connection_refused(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class", ("protocol_class", "transport_class"),
[ [
(_deprecated_TPLinkSmartHomeProtocol, XorTransport), (_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport), (IotProtocol, XorTransport),
@ -135,7 +135,7 @@ async def test_protocol_retry_recoverable_error(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class", ("protocol_class", "transport_class", "encryption_class"),
[ [
( (
_deprecated_TPLinkSmartHomeProtocol, _deprecated_TPLinkSmartHomeProtocol,
@ -185,7 +185,7 @@ async def test_protocol_reconnect(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class", ("protocol_class", "transport_class", "encryption_class"),
[ [
( (
_deprecated_TPLinkSmartHomeProtocol, _deprecated_TPLinkSmartHomeProtocol,
@ -239,7 +239,7 @@ async def test_protocol_handles_cancellation_during_write(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class", ("protocol_class", "transport_class", "encryption_class"),
[ [
( (
_deprecated_TPLinkSmartHomeProtocol, _deprecated_TPLinkSmartHomeProtocol,
@ -291,7 +291,7 @@ async def test_protocol_handles_cancellation_during_connection(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class", ("protocol_class", "transport_class", "encryption_class"),
[ [
( (
_deprecated_TPLinkSmartHomeProtocol, _deprecated_TPLinkSmartHomeProtocol,
@ -338,7 +338,7 @@ async def test_protocol_logging(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class", ("protocol_class", "transport_class", "encryption_class"),
[ [
( (
_deprecated_TPLinkSmartHomeProtocol, _deprecated_TPLinkSmartHomeProtocol,
@ -494,14 +494,10 @@ def test_protocol_init_signature(class_name_obj):
params = list(inspect.signature(class_name_obj[1].__init__).parameters.values()) params = list(inspect.signature(class_name_obj[1].__init__).parameters.values())
assert len(params) == 2 assert len(params) == 2
assert ( assert params[0].name == "self"
params[0].name == "self" assert params[0].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
and params[0].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD assert params[1].name == "transport"
) assert params[1].kind == inspect.Parameter.KEYWORD_ONLY
assert (
params[1].name == "transport"
and params[1].kind == inspect.Parameter.KEYWORD_ONLY
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -511,13 +507,10 @@ def test_transport_init_signature(class_name_obj):
params = list(inspect.signature(class_name_obj[1].__init__).parameters.values()) params = list(inspect.signature(class_name_obj[1].__init__).parameters.values())
assert len(params) == 2 assert len(params) == 2
assert ( assert params[0].name == "self"
params[0].name == "self" assert params[0].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
and params[0].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD assert params[1].name == "config"
) assert params[1].kind == inspect.Parameter.KEYWORD_ONLY
assert (
params[1].name == "config" and params[1].kind == inspect.Parameter.KEYWORD_ONLY
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -582,7 +575,7 @@ async def test_transport_credentials_hash(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"transport_class", "transport_class",
[AesTransport, KlapTransport, KlapTransportV2, XorTransport, XorTransport], [AesTransport, KlapTransport, KlapTransportV2, XorTransport],
) )
async def test_transport_credentials_hash_from_config(mocker, transport_class): async def test_transport_credentials_hash_from_config(mocker, transport_class):
"""Test that credentials_hash provided via config sets correctly.""" """Test that credentials_hash provided via config sets correctly."""
@ -599,7 +592,7 @@ async def test_transport_credentials_hash_from_config(mocker, transport_class):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"error, retry_expectation", ("error", "retry_expectation"),
[ [
(ConnectionRefusedError("dummy exception"), False), (ConnectionRefusedError("dummy exception"), False),
(OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), False), (OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), False),
@ -609,7 +602,7 @@ async def test_transport_credentials_hash_from_config(mocker, transport_class):
ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"), ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"),
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class", ("protocol_class", "transport_class"),
[ [
(_deprecated_TPLinkSmartHomeProtocol, XorTransport), (_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport), (IotProtocol, XorTransport),
@ -631,7 +624,7 @@ async def test_protocol_will_retry_on_connect(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"error, retry_expectation", ("error", "retry_expectation"),
[ [
(ConnectionRefusedError("dummy exception"), True), (ConnectionRefusedError("dummy exception"), True),
(OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), True), (OSError(errno.EHOSTDOWN, os.strerror(errno.EHOSTDOWN)), True),
@ -641,7 +634,7 @@ async def test_protocol_will_retry_on_connect(
ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"), ids=("ConnectionRefusedError", "OSErrorNoRetry", "OSErrorRetry", "Exception"),
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
"protocol_class, transport_class", ("protocol_class", "transport_class"),
[ [
(_deprecated_TPLinkSmartHomeProtocol, XorTransport), (_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport), (IotProtocol, XorTransport),

View File

@ -145,7 +145,7 @@ def test_tutorial_examples(readmes_mock):
assert not res["failed"] assert not res["failed"]
@pytest.fixture @pytest.fixture()
async def readmes_mock(mocker): async def readmes_mock(mocker):
fixture_infos = { fixture_infos = {
"127.0.0.1": get_fixture_info("KP303(UK)_1.0_1.0.3.json", "IOT"), # Strip "127.0.0.1": get_fixture_info("KP303(UK)_1.0_1.0.3.json", "IOT"), # Strip
@ -154,4 +154,4 @@ async def readmes_mock(mocker):
"127.0.0.4": get_fixture_info("KL430(US)_1.0_1.0.10.json", "IOT"), # Lightstrip "127.0.0.4": get_fixture_info("KL430(US)_1.0_1.0.10.json", "IOT"), # Lightstrip
"127.0.0.5": get_fixture_info("HS220(US)_1.0_1.5.7.json", "IOT"), # Dimmer "127.0.0.5": get_fixture_info("HS220(US)_1.0_1.5.7.json", "IOT"), # Dimmer
} }
yield patch_discovery(fixture_infos, mocker) return patch_discovery(fixture_infos, mocker)

View File

@ -65,7 +65,7 @@ async def test_smart_device_unknown_errors(
dummy_protocol._transport, "send", return_value=mock_response dummy_protocol._transport, "send", return_value=mock_response
) )
with pytest.raises(KasaException): with pytest.raises(KasaException): # noqa: PT012
res = await dummy_protocol.query(DUMMY_QUERY) res = await dummy_protocol.query(DUMMY_QUERY)
assert res is SmartErrorCode.INTERNAL_UNKNOWN_ERROR assert res is SmartErrorCode.INTERNAL_UNKNOWN_ERROR

View File

@ -20,7 +20,8 @@ def test_usage_convert_stat_data():
k, v = d.popitem() k, v = d.popitem()
assert isinstance(k, int) assert isinstance(k, int)
assert isinstance(v, int) assert isinstance(v, int)
assert k == 4 and v == 30 assert k == 4
assert v == 30
def test_usage_today(): def test_usage_today():

View File

@ -117,6 +117,7 @@ select = [
"FA", # flake8-future-annotations "FA", # flake8-future-annotations
"I", # isort "I", # isort
"S", # bandit "S", # bandit
"PT", # flake8-pytest-style
"LOG", # flake8-logging "LOG", # flake8-logging
"G", # flake8-logging-format "G", # flake8-logging-format
] ]