diff --git a/custom_components/econet300/api.py b/custom_components/econet300/api.py index 6a5f0b7..54cdf0c 100644 --- a/custom_components/econet300/api.py +++ b/custom_components/econet300/api.py @@ -36,7 +36,7 @@ def map_param(param_name): class Limits: """Class difining entity value set limits.""" - def __init__(self, min_v: float, max_v: float): + def __init__(self, min_v: int | None, max_v: int | None): """Construct the necessary attributes for the Limits object.""" self.min = min_v self.max = max_v diff --git a/custom_components/econet300/binary_sensor.py b/custom_components/econet300/binary_sensor.py index c3cf58b..3eb0a78 100644 --- a/custom_components/econet300/binary_sensor.py +++ b/custom_components/econet300/binary_sensor.py @@ -16,7 +16,7 @@ from .const import ( BINARY_SENSOR_MAP, DOMAIN, - ENTITY_DEVICE_CLASS_MAP, + ENTITY_BINARY_DEVICE_CLASS_MAP, ENTITY_ICON, ENTITY_ICON_OFF, SERVICE_API, @@ -27,7 +27,7 @@ _LOGGER = logging.getLogger(__name__) -@dataclass(frozen=True) +@dataclass class EconetBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes Econet binary sensor entity.""" @@ -80,7 +80,7 @@ def create_binary_entity_description(key: str) -> EconetBinarySensorEntityDescri entity_description = EconetBinarySensorEntityDescription( key=key, translation_key=camel_to_snake(map_key), - device_class=ENTITY_DEVICE_CLASS_MAP.get(map_key, None), + device_class=ENTITY_BINARY_DEVICE_CLASS_MAP.get(map_key, None), icon=ENTITY_ICON.get(map_key, None), icon_off=ENTITY_ICON_OFF.get(map_key, None), ) diff --git a/custom_components/econet300/const.py b/custom_components/econet300/const.py index 38b2d2f..9f2c0ce 100644 --- a/custom_components/econet300/const.py +++ b/custom_components/econet300/const.py @@ -227,9 +227,12 @@ "servoMixer1": "servo_mixer_1", "Status_wifi": "wifi_status", "main_server": "main_server", - ############################# - ###### BINARY SENSORS ####### - ############################# +} + +############################# +###### BINARY SENSORS ####### +############################# +ENTITY_BINARY_DEVICE_CLASS_MAP = { "lighter": BinarySensorDeviceClass.RUNNING, "weatherControl": BinarySensorDeviceClass.RUNNING, "unseal": BinarySensorDeviceClass.RUNNING, diff --git a/custom_components/econet300/entity.py b/custom_components/econet300/entity.py index 58994fc..b7c9c4d 100644 --- a/custom_components/econet300/entity.py +++ b/custom_components/econet300/entity.py @@ -1,4 +1,5 @@ """Base econet entity class.""" + import logging from homeassistant.core import callback @@ -21,8 +22,16 @@ class EconetEntity(CoordinatorEntity): """Representes EconetEntity.""" - api: Econet300Api - entity_description: EntityDescription + def __init__( + self, + coordinator: EconetDataCoordinator, + api: Econet300Api, + entity_description: EntityDescription, + ): + """Initialize the Econet entity.""" + super().__init__(coordinator) + self.api = api + self.entity_description = entity_description @property def has_entity_name(self): @@ -103,8 +112,7 @@ def __init__( idx: int, ): """Initialize the MixerEntity.""" - super().__init__(description, coordinator, api) - + super().__init__(coordinator, api, description) self._idx = idx @property diff --git a/pyproject.toml b/pyproject.toml index b083173..64f4812 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,8 @@ description = "ecoNET300 Home Assistant integration" authors = ["Jon "] readme = "README.md" +requires-python = ">=3.12.0" + [tool.poetry.dependencies] python = "~3.12.0" pymammotion = "0.2.66" @@ -35,6 +37,10 @@ load-plugins = [ "pylint.extensions.code_style", "pylint.extensions.typing", ] +persistent = false + +[tool.pylint.BASIC] +class-const-naming-style = "any" [tool.pylint."MESSAGES CONTROL"] # Reasons disabled: @@ -50,7 +56,7 @@ load-plugins = [ # inconsistent-return-statements - doesn't handle raise # too-many-ancestors - it's too strict. # wrong-import-order - isort guards this -# consider-using-f-string - str.format sometimes more readable +# possibly-used-before-assignment - too many errors / not necessarily issues # --- # Pylint CodeStyle plugin # consider-using-namedtuple-or-dataclass - too opinionated @@ -71,10 +77,11 @@ disable = [ "too-many-locals", "too-many-public-methods", "too-many-boolean-expressions", + "too-many-positional-arguments", "wrong-import-order", - "consider-using-f-string", "consider-using-namedtuple-or-dataclass", "consider-using-assignment-expr", + "possibly-used-before-assignment", # Handled by ruff # Ref: @@ -84,6 +91,7 @@ disable = [ "bidirectional-unicode", # PLE2502 "continue-in-finally", # PLE0116 "duplicate-bases", # PLE0241 + "misplaced-bare-raise", # PLE0704 "format-needs-mapping", # F502 "function-redefined", # F811 # Needed because ruff does not understand type of __all__ generated by a function @@ -211,6 +219,8 @@ disable = [ "no-else-return", # RET505 "broad-except", # BLE001 "protected-access", # SLF001 + "broad-exception-raised", # TRY002 + "consider-using-f-string", # PLC0209 # "no-self-use", # PLR6301 # Optional plugin, not enabled # Handled by mypy @@ -325,11 +335,18 @@ runtime-typing = false max-line-length-suggestions = 72 [tool.ruff] -required-version = ">=0.4.3" +required-version = ">=0.6.8" + [tool.ruff.lint] select = [ "A001", # Variable {name} is shadowing a Python builtin + "ASYNC210", # Async functions should not call blocking HTTP methods + "ASYNC220", # Async functions should not create subprocesses with blocking methods + "ASYNC221", # Async functions should not run processes with blocking methods + "ASYNC222", # Async functions should not wait on processes with blocking methods + "ASYNC230", # Async functions should not open files with blocking methods like open + "ASYNC251", # Async functions should not call time.sleep "B002", # Python does not support the unary prefix increment "B005", # Using .strip() with multi-character strings is misleading "B007", # Loop control variable {name} not used within loop body @@ -342,6 +359,7 @@ select = [ "B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)? "B904", # Use raise from to specify exception cause "B905", # zip() without an explicit strict= parameter + "BLE", "C", # complexity "COM818", # Trailing comma on bare tuple prohibited "D", # docstrings @@ -349,7 +367,9 @@ select = [ "DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts) "E", # pycodestyle "F", # pyflakes/autoflake + "F541", # f-string without any placeholders "FLY", # flynt + "FURB", # refurb "G", # flake8-logging-format "I", # isort "INP", # flake8-no-pep420 @@ -364,12 +384,15 @@ select = [ "PIE", # flake8-pie "PL", # pylint "PT", # flake8-pytest-style + "PTH", # flake8-pathlib "PYI", # flake8-pyi "RET", # flake8-return "RSE", # flake8-raise "RUF005", # Consider iterable unpacking instead of concatenation "RUF006", # Store a reference to the return value of asyncio.create_task + "RUF010", # Use explicit conversion flag "RUF013", # PEP 484 prohibits implicit Optional + "RUF017", # Avoid quadratic list summation "RUF018", # Avoid assignment expressions in assert statements "RUF019", # Unnecessary key check before dictionary access # "RUF100", # Unused `noqa` directive; temporarily every now and then to clean them up @@ -392,12 +415,16 @@ select = [ "S608", # hardcoded-sql-expression "S609", # unix-command-wildcard-injection "SIM", # flake8-simplify + "SLF", # flake8-self "SLOT", # flake8-slots "T100", # Trace found: {name} used "T20", # flake8-print - "TID251", # Banned imports + "TCH", # flake8-type-checking + "TID", # Tidy imports "TRY", # tryceratops "UP", # pyupgrade + "UP031", # Use format specifiers instead of percent format + "UP032", # Use f-string instead of `format` call "W", # pycodestyle ] @@ -418,21 +445,25 @@ ignore = [ "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target "PT004", # Fixture {fixture} does not return anything, add leading underscore "PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception - "PT012", # `pytest.raises()` block should contain a single simple statement "PT018", # Assertion should be broken down into multiple parts "RUF001", # String contains ambiguous unicode character. "RUF002", # Docstring contains ambiguous unicode character. "RUF003", # Comment contains ambiguous unicode character. "RUF015", # Prefer next(...) over single element slice "SIM102", # Use a single if statement instead of nested if statements + "SIM103", # Return the condition {condition} directly "SIM108", # Use ternary operator {contents} instead of if-else-block "SIM115", # Use context handler for opening files + + # Moving imports into type-checking blocks can mess with pytest.patch() + "TCH001", # Move application import {} into a type-checking block + "TCH002", # Move third-party import {} into a type-checking block + "TCH003", # Move standard library import {} into a type-checking block + "TRY003", # Avoid specifying long messages outside the exception class "TRY400", # Use `logging.exception` instead of `logging.error` # Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923 "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` - # Ignored due to incompatible with mypy: https://github.com/python/mypy/issues/15238 - "UP040", # Checks for use of TypeAlias annotation for declaring type aliases. # May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules "W191", @@ -447,21 +478,54 @@ ignore = [ "ISC001", # Disabled because ruff does not understand type of __all__ generated by a function - "PLE0605", - - # temporarily disabled - "PT019", - "PYI024", # Use typing.NamedTuple instead of collections.namedtuple - "RET503", - "RET502", - "RET501", - "TRY002", - "TRY301" + "PLE0605" ] -[tool.ruff.flake8-import-conventions.extend-aliases] +[tool.ruff.lint.flake8-import-conventions.extend-aliases] voluptuous = "vol" +"homeassistant.components.air_quality.PLATFORM_SCHEMA" = "AIR_QUALITY_PLATFORM_SCHEMA" +"homeassistant.components.alarm_control_panel.PLATFORM_SCHEMA" = "ALARM_CONTROL_PANEL_PLATFORM_SCHEMA" +"homeassistant.components.binary_sensor.PLATFORM_SCHEMA" = "BINARY_SENSOR_PLATFORM_SCHEMA" +"homeassistant.components.button.PLATFORM_SCHEMA" = "BUTTON_PLATFORM_SCHEMA" +"homeassistant.components.calendar.PLATFORM_SCHEMA" = "CALENDAR_PLATFORM_SCHEMA" +"homeassistant.components.camera.PLATFORM_SCHEMA" = "CAMERA_PLATFORM_SCHEMA" +"homeassistant.components.climate.PLATFORM_SCHEMA" = "CLIMATE_PLATFORM_SCHEMA" +"homeassistant.components.conversation.PLATFORM_SCHEMA" = "CONVERSATION_PLATFORM_SCHEMA" +"homeassistant.components.cover.PLATFORM_SCHEMA" = "COVER_PLATFORM_SCHEMA" +"homeassistant.components.date.PLATFORM_SCHEMA" = "DATE_PLATFORM_SCHEMA" +"homeassistant.components.datetime.PLATFORM_SCHEMA" = "DATETIME_PLATFORM_SCHEMA" +"homeassistant.components.device_tracker.PLATFORM_SCHEMA" = "DEVICE_TRACKER_PLATFORM_SCHEMA" +"homeassistant.components.event.PLATFORM_SCHEMA" = "EVENT_PLATFORM_SCHEMA" +"homeassistant.components.fan.PLATFORM_SCHEMA" = "FAN_PLATFORM_SCHEMA" +"homeassistant.components.geo_location.PLATFORM_SCHEMA" = "GEO_LOCATION_PLATFORM_SCHEMA" +"homeassistant.components.humidifier.PLATFORM_SCHEMA" = "HUMIDIFIER_PLATFORM_SCHEMA" +"homeassistant.components.image.PLATFORM_SCHEMA" = "IMAGE_PLATFORM_SCHEMA" +"homeassistant.components.image_processing.PLATFORM_SCHEMA" = "IMAGE_PROCESSING_PLATFORM_SCHEMA" +"homeassistant.components.lawn_mower.PLATFORM_SCHEMA" = "LAWN_MOWER_PLATFORM_SCHEMA" +"homeassistant.components.light.PLATFORM_SCHEMA" = "LIGHT_PLATFORM_SCHEMA" +"homeassistant.components.lock.PLATFORM_SCHEMA" = "LOCK_PLATFORM_SCHEMA" +"homeassistant.components.media_player.PLATFORM_SCHEMA" = "MEDIA_PLAYER_PLATFORM_SCHEMA" +"homeassistant.components.notify.PLATFORM_SCHEMA" = "NOTIFY_PLATFORM_SCHEMA" +"homeassistant.components.number.PLATFORM_SCHEMA" = "NUMBER_PLATFORM_SCHEMA" +"homeassistant.components.remote.PLATFORM_SCHEMA" = "REMOTE_PLATFORM_SCHEMA" +"homeassistant.components.scene.PLATFORM_SCHEMA" = "SCENE_PLATFORM_SCHEMA" +"homeassistant.components.select.PLATFORM_SCHEMA" = "SELECT_PLATFORM_SCHEMA" +"homeassistant.components.sensor.PLATFORM_SCHEMA" = "SENSOR_PLATFORM_SCHEMA" +"homeassistant.components.siren.PLATFORM_SCHEMA" = "SIREN_PLATFORM_SCHEMA" +"homeassistant.components.stt.PLATFORM_SCHEMA" = "STT_PLATFORM_SCHEMA" +"homeassistant.components.switch.PLATFORM_SCHEMA" = "SWITCH_PLATFORM_SCHEMA" +"homeassistant.components.text.PLATFORM_SCHEMA" = "TEXT_PLATFORM_SCHEMA" +"homeassistant.components.time.PLATFORM_SCHEMA" = "TIME_PLATFORM_SCHEMA" +"homeassistant.components.todo.PLATFORM_SCHEMA" = "TODO_PLATFORM_SCHEMA" +"homeassistant.components.tts.PLATFORM_SCHEMA" = "TTS_PLATFORM_SCHEMA" +"homeassistant.components.vacuum.PLATFORM_SCHEMA" = "VACUUM_PLATFORM_SCHEMA" +"homeassistant.components.valve.PLATFORM_SCHEMA" = "VALVE_PLATFORM_SCHEMA" +"homeassistant.components.update.PLATFORM_SCHEMA" = "UPDATE_PLATFORM_SCHEMA" +"homeassistant.components.wake_word.PLATFORM_SCHEMA" = "WAKE_WORD_PLATFORM_SCHEMA" +"homeassistant.components.water_heater.PLATFORM_SCHEMA" = "WATER_HEATER_PLATFORM_SCHEMA" +"homeassistant.components.weather.PLATFORM_SCHEMA" = "WEATHER_PLATFORM_SCHEMA" +"homeassistant.core.DOMAIN" = "HOMEASSISTANT_DOMAIN" "homeassistant.helpers.area_registry" = "ar" "homeassistant.helpers.category_registry" = "cr" "homeassistant.helpers.config_validation" = "cv" @@ -475,6 +539,7 @@ voluptuous = "vol" [tool.ruff.flake8-pytest-style] fixture-parentheses = false mark-parentheses = false + [tool.ruff.flake8-tidy-imports.banned-api] "async_timeout".msg = "use asyncio.timeout instead" "pytz".msg = "use zoneinfo instead" diff --git a/requirements.txt b/requirements.txt index f9ecf1f..ad90913 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ colorlog==6.8.2 homeassistant>=2024.9.2 -ruff==0.6.5 +ruff==0.6.8 codespell==2.3.0 \ No newline at end of file