Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Enable ruff check for ANN #1139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 10, 2024
Merged

Enable ruff check for ANN #1139

merged 10 commits into from
Nov 10, 2024

Conversation

rytilahti
Copy link
Member

@rytilahti rytilahti commented Sep 28, 2024

This PR annotates the code base and enables ANN for ruff.
This makes the API contracts cleaner & avoids issues on untyped APIs leveraged by downstreams (given we are signaling being typed through py.typed).

@sdb9696
Copy link
Collaborator

sdb9696 commented Sep 28, 2024

So I'm not sure why you're getting those errors but I quickly checked out your PR and ran pre-commit and I got a lot mypy errors, some of which looked like they might be due to ruff auto fixes? Anyway I rebased successfully and got the same mypy errors as before so pushed them to a new branch in case you wanted to take that one over. #1143

@sdb9696 sdb9696 mentioned this pull request Sep 28, 2024
@rytilahti
Copy link
Member Author

Looks like something is really wrong in my system, getting those errors also after rebasing #1137 on top of the current master. Either ruff/some other tool is messing with it, I suppose.

@sdb9696
Copy link
Collaborator

sdb9696 commented Sep 28, 2024

I’ve recently uninstalled uv from pipx and reinstalled from the standalone installer. Might be worth trying that and deleting the venv and starting again with a fresh git clone.

@sdb9696
Copy link
Collaborator

sdb9696 commented Sep 30, 2024

Actually just looking at your error it seems like it is coming from pypy. I get errors if I try to run our pre-commit checks from pypy, maybe you should try re-syncing your env with cpython3.12 and running pre-commit clean.

"""Encrypt the message."""
encryptor = self.cipher.encryptor()
padder = self.padding_strategy.padder()
padded_data = padder.update(data) + padder.finalize()
encrypted = encryptor.update(padded_data) + encryptor.finalize()
return base64.b64encode(encrypted)

def decrypt(self, data) -> str:
def decrypt(self, data: str | bytes) -> str:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be cleaner if we'd accept only bytes here (and leave base64-decoding to the caller), but I feel this is fine as it is.

Comment on lines +260 to 270
async def turn_on(self, **kwargs) -> dict:
"""Turn on the device."""

@abstractmethod
async def turn_off(self, **kwargs) -> dict | None:
async def turn_off(self, **kwargs) -> dict:
"""Turn off the device."""

@abstractmethod
async def set_state(self, on: bool):
async def set_state(self, on: bool) -> dict:
"""Set the device state to *on*.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All setters are now expected to return a dict, preferably something directly from the device. This clarifies our API contract and avoids the need to check for Noneness on responses.

if is_dataclass(klass):
fieldtypes = {f.name: f.type for f in fields(klass)}
val = {}
for dict_key in in_val:
if dict_key in fieldtypes:
if hasattr(fieldtypes[dict_key], "from_dict"):
val[dict_key] = fieldtypes[dict_key].from_dict(in_val[dict_key])
val[dict_key] = fieldtypes[dict_key].from_dict(in_val[dict_key]) # type: ignore[union-attr]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignored this for now, especially if we are going to move to use mashumaro for handling also these in the future.

@@ -91,12 +91,12 @@ def _dataclass_from_dict(klass, in_val):
raise KasaException(
f"Cannot create dataclass from dict, unknown key: {dict_key}"
)
return klass(**val)
return klass(**val) # type: ignore[operator]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignored this for now, especially if we are going to move to use mashumaro for handling also these in the future.

Comment on lines +153 to +154
OnDiscoveredCallable = Callable[[Device], Coroutine]
OnUnsupportedCallable = Callable[[UnsupportedDeviceError], Coroutine]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are calling create_task which expects a more strict Coroutine instead of an awaitable.

@@ -31,34 +33,34 @@ def current_consumption(self) -> float | None:
# Fallback if get_energy_usage does not provide current_power,
# which can happen on some newer devices (e.g. P304M).
elif (
power := self.data.get("get_current_power").get("current_power")
power := self.data.get("get_current_power", {}).get("current_power")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoids checking separately for noneness.

Comment on lines +72 to +78
return self.energy.get("month_energy", 0) / 1_000

@property
@raise_if_update_error
def consumption_today(self) -> float | None:
"""Get the emeter value for today in kWh."""
return self.energy.get("today_energy") / 1_000
return self.energy.get("today_energy", 0) / 1_000
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoids dividing none if the data is not available for some reason, should be fine.

if (
mod.REQUIRED_COMPONENT in self._components
or self.sys_info.get(mod.REQUIRED_KEY_ON_PARENT) is not None
required_component = cast(str, mod.REQUIRED_COMPONENT)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUIRED_COMPONENT is typed with str | None, but it should not be none here.

@@ -610,30 +614,33 @@ def is_on(self) -> bool:
"""Return true if the device is on."""
return bool(self._info.get("device_on"))

async def set_state(self, on: bool): # TODO: better name wanted.
async def set_state(self, on: bool) -> dict:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing stael comment, as this is now part of our public API.

Comment on lines +25 to +26
func: Callable[Concatenate[_T, _P], Awaitable[dict]],
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, dict]]:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm not that familiar complex types, but I assume this is correct for cases where we are expecting dict as return? I'm wondering if both of these could/should be either Coroutines or Awaitables?

@rytilahti rytilahti marked this pull request as ready for review November 10, 2024 16:27
@rytilahti rytilahti requested a review from sdb9696 November 10, 2024 16:31
@rytilahti rytilahti added maintenance Project improvements and maintenance enhancement New feature or request and removed enhancement New feature or request labels Nov 10, 2024
Copy link

codecov bot commented Nov 10, 2024

Codecov Report

Attention: Patch coverage is 96.28647% with 14 lines in your changes missing coverage. Please review.

Project coverage is 92.62%. Comparing base (6b44fe6) to head (a08537d).
Report is 196 commits behind head on master.

Files with missing lines Patch % Lines
kasa/iot/iotdevice.py 83.33% 3 Missing ⚠️
kasa/cli/usage.py 75.00% 2 Missing ⚠️
kasa/iot/iotdimmer.py 80.00% 2 Missing ⚠️
kasa/smart/smartdevice.py 92.00% 2 Missing ⚠️
kasa/cli/main.py 66.66% 1 Missing ⚠️
kasa/device.py 93.33% 1 Missing ⚠️
kasa/iot/iotstrip.py 93.75% 1 Missing ⚠️
kasa/iot/modules/rulemodule.py 80.00% 1 Missing ⚠️
kasa/module.py 85.71% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1139   +/-   ##
=======================================
  Coverage   92.62%   92.62%           
=======================================
  Files         101      101           
  Lines        6630     6644   +14     
  Branches      709      706    -3     
=======================================
+ Hits         6141     6154   +13     
- Misses        368      370    +2     
+ Partials      121      120    -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Collaborator

@sdb9696 sdb9696 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great step forwards thanks @rytilahti!

@sdb9696 sdb9696 merged commit 66eb170 into master Nov 10, 2024
28 checks passed
@sdb9696 sdb9696 deleted the janitor/ruff_ann branch November 10, 2024 18:55
@sdb9696 sdb9696 mentioned this pull request Nov 25, 2024
sdb9696 added a commit that referenced this pull request Nov 26, 2024
## [0.8.0](https://github.com/python-kasa/python-kasa/tree/0.8.0) (2024-11-26)

[Full Changelog](0.7.7...0.8.0)

**Release highlights:**

- **Initial support for devices using the Tapo camera protocol, i.e. Tapo cameras and the Tapo H200 hub.**
- New camera functionality such as exposing RTSP streaming urls and camera pan/tilt.
- New way of testing module support for individual features with `has_feature` and `get_feature`.
- Adding voltage and current monitoring to `smart` devices.
- Migration from pydantic to mashumaro for serialization.

Special thanks to @ryenitcher and @Puxtril for their new contributions to the improvement of the project! Also thanks to everyone who has helped with testing, contributing fixtures, and reporting issues!

**Breaking change notes:**

- Removed support for python <3.11. If you haven't got a compatible version try [uv](https://docs.astral.sh/uv/).
- Renamed `device_config.to_dict()` to `device_config.to_dict_control_credentials()`. `to_dict()` is still available but takes no parameters.
- From the `iot.Cloud` module the `iot.CloudInfo` class attributes have been converted to snake case.


**Breaking changes:**

- Migrate iot cloud module to mashumaro [\#1282](#1282) (@sdb9696)
- Replace custom deviceconfig serialization with mashumaru [\#1274](#1274) (@sdb9696)
- Remove support for python \<3.11 [\#1273](#1273) (@sdb9696)

**Implemented enhancements:**

- Update cli modify presets to support smart devices [\#1295](#1295) (@sdb9696)
- Use credentials\_hash for smartcamera rtsp url [\#1293](#1293) (@sdb9696)
- Add voltage and current monitoring to smart Devices [\#1281](#1281) (@ryenitcher)
- Update cli feature command for actions not to require a value [\#1264](#1264) (@sdb9696)
- Add pan tilt camera module [\#1261](#1261) (@sdb9696)
- Add alarm module for smartcamera hubs [\#1258](#1258) (@sdb9696)
- Move TAPO smartcamera out of experimental package [\#1255](#1255) (@sdb9696)
- Add SmartCamera Led Module [\#1249](#1249) (@sdb9696)
- Use component queries to select smartcamera modules [\#1248](#1248) (@sdb9696)
- Print formatting for IotLightPreset [\#1216](#1216) (@Puxtril)
- Allow getting Annotated features from modules [\#1018](#1018) (@sdb9696)
- Add common Thermostat module [\#977](#977) (@sdb9696)

**Fixed bugs:**

- TP-Link Tapo S505D cannot disable gradual on/off  [\#1309](#1309)
- Inconsistent emeter information between features and emeter cli [\#1308](#1308)
- How to dump power usage after latest updates? [\#1306](#1306)
- kasa.discover: Got unsupported connection type: 'device\_family': 'SMART.IPCAMERA' [\#1267](#1267)
- device \_\_repr\_\_ fails if no sys\_info [\#1262](#1262)
- Tapo P110M: Error processing Energy for device, module will be unavailable: get\_energy\_usage for Energy  [\#1243](#1243)
- Listing light presets throws error [\#1201](#1201)
- Include duration when disabling smooth transition on/off [\#1313](#1313) (@rytilahti)
- Expose energy command to cli [\#1307](#1307) (@rytilahti)
- Make discovery on unsupported devices less noisy [\#1291](#1291) (@rytilahti)
- Fix repr for device created with no sysinfo or discovery info" [\#1266](#1266) (@sdb9696)
- Fix discovery by alias for smart devices [\#1260](#1260) (@sdb9696)
- Make \_\_repr\_\_ work on discovery info [\#1233](#1233) (@rytilahti)

**Added support for devices:**

- Add HS200 \(US\) Smart Fixture [\#1303](#1303) (@ZeliardM)
- Add smartcamera devices to supported docs [\#1257](#1257) (@sdb9696)
- Add P110M\(AU\) fixture [\#1244](#1244) (@rytilahti)
- Add L630 fixture [\#1240](#1240) (@rytilahti)
- Add EP40M Fixture [\#1238](#1238) (@ryenitcher)
- Add KS220 Fixture [\#1237](#1237) (@ryenitcher)

**Documentation updates:**

- Use markdown footnotes in supported.md [\#1310](#1310) (@sdb9696)
- Update docs for the new module attributes has/get feature [\#1301](#1301) (@sdb9696)
- Fixup contributing.md for running test against a real device [\#1236](#1236) (@sdb9696)

**Project maintenance:**

- Rename tests/smartcamera to tests/smartcam [\#1315](#1315) (@sdb9696)
- Do not error on smartcam hub attached smartcam child devices [\#1314](#1314) (@sdb9696)
- Add P110M\(EU\) fixture [\#1305](#1305) (@sdb9696)
- Run tests with caplog in a single worker [\#1304](#1304) (@sdb9696)
- Rename smartcamera to smartcam [\#1300](#1300) (@sdb9696)
- Move iot fixtures into iot subfolder [\#1299](#1299) (@sdb9696)
- Annotate fan\_speed\_level of Fan interface [\#1298](#1298) (@sdb9696)
- Add PIR ADC Values to Test Fixtures [\#1296](#1296) (@ryenitcher)
- Exclude \_\_getattr\_\_ for deprecated attributes from type checkers [\#1294](#1294) (@sdb9696)
- Simplify omit http\_client in DeviceConfig serialization [\#1292](#1292) (@sdb9696)
- Add SMART Voltage Monitoring to Fixtures [\#1290](#1290) (@ryenitcher)
- Remove pydantic dependency [\#1289](#1289) (@sdb9696)
- Do not print out all the fixture names at the start of test runs [\#1287](#1287) (@sdb9696)
- dump\_devinfo: iot light strip commands [\#1286](#1286) (@sdb9696)
- Migrate TurnOnBehaviours to mashumaro [\#1285](#1285) (@sdb9696)
- dump\_devinfo: query smartlife.iot.common.cloud for fw updates [\#1284](#1284) (@rytilahti)
- Migrate RuleModule to mashumaro [\#1283](#1283) (@sdb9696)
- Update sphinx dependency to 6.2 to fix docs build [\#1280](#1280) (@sdb9696)
- Update DiscoveryResult to use mashu Annotated Alias [\#1279](#1279) (@sdb9696)
- Extend dump\_devinfo iot queries [\#1278](#1278) (@sdb9696)
- Migrate triggerlogs to mashumaru [\#1277](#1277) (@sdb9696)
- Migrate smart firmware module to mashumaro [\#1276](#1276) (@sdb9696)
- Migrate IotLightPreset to mashumaru [\#1275](#1275) (@sdb9696)
- Allow callable coroutines for feature setters [\#1272](#1272) (@sdb9696)
- Fix deprecated SSLContext\(\) usage [\#1271](#1271) (@sdb9696)
- Use \_get\_device\_info methods for smart and iot devs in devtools [\#1265](#1265) (@sdb9696)
- Remove experimental support [\#1256](#1256) (@sdb9696)
- Move protocol modules into protocols package [\#1254](#1254) (@sdb9696)
- Add linkcheck to readthedocs CI [\#1253](#1253) (@rytilahti)
- Update cli energy command to use energy module [\#1252](#1252) (@sdb9696)
- Consolidate warnings for fixtures missing child devices [\#1251](#1251) (@sdb9696)
- Update smartcamera fixtures with components [\#1250](#1250) (@sdb9696)
- Move transports into their own package [\#1247](#1247) (@rytilahti)
- Fix warnings in our test suite [\#1246](#1246) (@rytilahti)
- Move tests folder to top level of project [\#1242](#1242) (@sdb9696)
- Fix test framework running against real devices [\#1235](#1235) (@sdb9696)
- Add Additional Firmware Test Fixures [\#1234](#1234) (@ryenitcher)
- Update DiscoveryResult to use Mashumaro instead of pydantic [\#1231](#1231) (@sdb9696)
- Update fixture for ES20M 1.0.11 [\#1215](#1215) (@rytilahti)
- Enable ruff check for ANN [\#1139](#1139) (@rytilahti)

**Closed issues:**

- Expose Fan speed range from the library [\#1008](#1008)
- \[META\] 0.7 series - module support for SMART devices, support for introspectable device features and refactoring the library [\#783](#783)
rytilahti pushed a commit that referenced this pull request Nov 29, 2024
## [0.8.0](https://github.com/python-kasa/python-kasa/tree/0.8.0) (2024-11-26)

[Full Changelog](0.7.7...0.8.0)

**Release highlights:**

- **Initial support for devices using the Tapo camera protocol, i.e. Tapo cameras and the Tapo H200 hub.**
- New camera functionality such as exposing RTSP streaming urls and camera pan/tilt.
- New way of testing module support for individual features with `has_feature` and `get_feature`.
- Adding voltage and current monitoring to `smart` devices.
- Migration from pydantic to mashumaro for serialization.

Special thanks to @ryenitcher and @Puxtril for their new contributions to the improvement of the project! Also thanks to everyone who has helped with testing, contributing fixtures, and reporting issues!

**Breaking change notes:**

- Removed support for python <3.11. If you haven't got a compatible version try [uv](https://docs.astral.sh/uv/).
- Renamed `device_config.to_dict()` to `device_config.to_dict_control_credentials()`. `to_dict()` is still available but takes no parameters.
- From the `iot.Cloud` module the `iot.CloudInfo` class attributes have been converted to snake case.


**Breaking changes:**

- Migrate iot cloud module to mashumaro [\#1282](#1282) (@sdb9696)
- Replace custom deviceconfig serialization with mashumaru [\#1274](#1274) (@sdb9696)
- Remove support for python \<3.11 [\#1273](#1273) (@sdb9696)

**Implemented enhancements:**

- Update cli modify presets to support smart devices [\#1295](#1295) (@sdb9696)
- Use credentials\_hash for smartcamera rtsp url [\#1293](#1293) (@sdb9696)
- Add voltage and current monitoring to smart Devices [\#1281](#1281) (@ryenitcher)
- Update cli feature command for actions not to require a value [\#1264](#1264) (@sdb9696)
- Add pan tilt camera module [\#1261](#1261) (@sdb9696)
- Add alarm module for smartcamera hubs [\#1258](#1258) (@sdb9696)
- Move TAPO smartcamera out of experimental package [\#1255](#1255) (@sdb9696)
- Add SmartCamera Led Module [\#1249](#1249) (@sdb9696)
- Use component queries to select smartcamera modules [\#1248](#1248) (@sdb9696)
- Print formatting for IotLightPreset [\#1216](#1216) (@Puxtril)
- Allow getting Annotated features from modules [\#1018](#1018) (@sdb9696)
- Add common Thermostat module [\#977](#977) (@sdb9696)

**Fixed bugs:**

- TP-Link Tapo S505D cannot disable gradual on/off  [\#1309](#1309)
- Inconsistent emeter information between features and emeter cli [\#1308](#1308)
- How to dump power usage after latest updates? [\#1306](#1306)
- kasa.discover: Got unsupported connection type: 'device\_family': 'SMART.IPCAMERA' [\#1267](#1267)
- device \_\_repr\_\_ fails if no sys\_info [\#1262](#1262)
- Tapo P110M: Error processing Energy for device, module will be unavailable: get\_energy\_usage for Energy  [\#1243](#1243)
- Listing light presets throws error [\#1201](#1201)
- Include duration when disabling smooth transition on/off [\#1313](#1313) (@rytilahti)
- Expose energy command to cli [\#1307](#1307) (@rytilahti)
- Make discovery on unsupported devices less noisy [\#1291](#1291) (@rytilahti)
- Fix repr for device created with no sysinfo or discovery info" [\#1266](#1266) (@sdb9696)
- Fix discovery by alias for smart devices [\#1260](#1260) (@sdb9696)
- Make \_\_repr\_\_ work on discovery info [\#1233](#1233) (@rytilahti)

**Added support for devices:**

- Add HS200 \(US\) Smart Fixture [\#1303](#1303) (@ZeliardM)
- Add smartcamera devices to supported docs [\#1257](#1257) (@sdb9696)
- Add P110M\(AU\) fixture [\#1244](#1244) (@rytilahti)
- Add L630 fixture [\#1240](#1240) (@rytilahti)
- Add EP40M Fixture [\#1238](#1238) (@ryenitcher)
- Add KS220 Fixture [\#1237](#1237) (@ryenitcher)

**Documentation updates:**

- Use markdown footnotes in supported.md [\#1310](#1310) (@sdb9696)
- Update docs for the new module attributes has/get feature [\#1301](#1301) (@sdb9696)
- Fixup contributing.md for running test against a real device [\#1236](#1236) (@sdb9696)

**Project maintenance:**

- Rename tests/smartcamera to tests/smartcam [\#1315](#1315) (@sdb9696)
- Do not error on smartcam hub attached smartcam child devices [\#1314](#1314) (@sdb9696)
- Add P110M\(EU\) fixture [\#1305](#1305) (@sdb9696)
- Run tests with caplog in a single worker [\#1304](#1304) (@sdb9696)
- Rename smartcamera to smartcam [\#1300](#1300) (@sdb9696)
- Move iot fixtures into iot subfolder [\#1299](#1299) (@sdb9696)
- Annotate fan\_speed\_level of Fan interface [\#1298](#1298) (@sdb9696)
- Add PIR ADC Values to Test Fixtures [\#1296](#1296) (@ryenitcher)
- Exclude \_\_getattr\_\_ for deprecated attributes from type checkers [\#1294](#1294) (@sdb9696)
- Simplify omit http\_client in DeviceConfig serialization [\#1292](#1292) (@sdb9696)
- Add SMART Voltage Monitoring to Fixtures [\#1290](#1290) (@ryenitcher)
- Remove pydantic dependency [\#1289](#1289) (@sdb9696)
- Do not print out all the fixture names at the start of test runs [\#1287](#1287) (@sdb9696)
- dump\_devinfo: iot light strip commands [\#1286](#1286) (@sdb9696)
- Migrate TurnOnBehaviours to mashumaro [\#1285](#1285) (@sdb9696)
- dump\_devinfo: query smartlife.iot.common.cloud for fw updates [\#1284](#1284) (@rytilahti)
- Migrate RuleModule to mashumaro [\#1283](#1283) (@sdb9696)
- Update sphinx dependency to 6.2 to fix docs build [\#1280](#1280) (@sdb9696)
- Update DiscoveryResult to use mashu Annotated Alias [\#1279](#1279) (@sdb9696)
- Extend dump\_devinfo iot queries [\#1278](#1278) (@sdb9696)
- Migrate triggerlogs to mashumaru [\#1277](#1277) (@sdb9696)
- Migrate smart firmware module to mashumaro [\#1276](#1276) (@sdb9696)
- Migrate IotLightPreset to mashumaru [\#1275](#1275) (@sdb9696)
- Allow callable coroutines for feature setters [\#1272](#1272) (@sdb9696)
- Fix deprecated SSLContext\(\) usage [\#1271](#1271) (@sdb9696)
- Use \_get\_device\_info methods for smart and iot devs in devtools [\#1265](#1265) (@sdb9696)
- Remove experimental support [\#1256](#1256) (@sdb9696)
- Move protocol modules into protocols package [\#1254](#1254) (@sdb9696)
- Add linkcheck to readthedocs CI [\#1253](#1253) (@rytilahti)
- Update cli energy command to use energy module [\#1252](#1252) (@sdb9696)
- Consolidate warnings for fixtures missing child devices [\#1251](#1251) (@sdb9696)
- Update smartcamera fixtures with components [\#1250](#1250) (@sdb9696)
- Move transports into their own package [\#1247](#1247) (@rytilahti)
- Fix warnings in our test suite [\#1246](#1246) (@rytilahti)
- Move tests folder to top level of project [\#1242](#1242) (@sdb9696)
- Fix test framework running against real devices [\#1235](#1235) (@sdb9696)
- Add Additional Firmware Test Fixures [\#1234](#1234) (@ryenitcher)
- Update DiscoveryResult to use Mashumaro instead of pydantic [\#1231](#1231) (@sdb9696)
- Update fixture for ES20M 1.0.11 [\#1215](#1215) (@rytilahti)
- Enable ruff check for ANN [\#1139](#1139) (@rytilahti)

**Closed issues:**

- Expose Fan speed range from the library [\#1008](#1008)
- \[META\] 0.7 series - module support for SMART devices, support for introspectable device features and refactoring the library [\#783](#783)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintenance Project improvements and maintenance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants