diff --git a/.devcontainer.json b/.devcontainer.json index 3e32baf..8033368 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -37,5 +37,13 @@ } }, "remoteUser": "vscode", - "features": {} + "features": { + "ghcr.io/devcontainers-extra/features/apt-packages:1": { + "packages": [ + "ffmpeg", + "libturbojpeg0", + "libpcap-dev" + ] + } + } } diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index a3968bd..fffe319 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -44,7 +44,7 @@ body: - type: textarea attributes: label: "Debug logs" - description: "To enable debug logs check this https://www.home-assistant.io/integrations/logger/, this **needs** to install _everything_ from startup of Home Assistant to the point where you encounter the issue." + description: "To enable debug logs check this https://www.home-assistant.io/integrations/logger/, this **needs** to include _everything_ from startup of Home Assistant to the point where you encounter the issue." render: text validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 83e9cfa..0d68b1e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -67,7 +67,8 @@ - [ ] The code change is tested and works locally. - [ ] There is no commented out code in this PR. -- [ ] The code has been formatted using Black (`black --fast custom_components`) +- [ ] The code has been formatted using Ruff (`python3 -m ruff format custom_components`) +- [ ] The code quality has been checked using Ruff (`python3 -m ruff check custom_components --fix`) If user exposed functionality or configuration variables are added/changed: diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml new file mode 100644 index 0000000..04c38b4 --- /dev/null +++ b/.github/workflows/no-response.yml @@ -0,0 +1,18 @@ +name: No Response + +# Both `issue_comment` and `scheduled` event types are required for this Action +# to work properly. +on: + issue_comment: + types: [created] + schedule: + # Schedule for five minutes after the hour, every hour + - cron: '5 * * * *' + +jobs: + noResponse: + runs-on: ubuntu-latest + steps: + - uses: lee-dohm/no-response@v0.5.0 + with: + token: ${{ github.token }} diff --git a/.github/workflows/py-dead-code.yml b/.github/workflows/py-dead-code.yml deleted file mode 100644 index 5e3744b..0000000 --- a/.github/workflows/py-dead-code.yml +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: "Python Find Dead Code" - -on: [pull_request] - -jobs: - lint: - name: "Find Dead Code" - runs-on: ubuntu-latest - steps: - - name: "Checkout code" - uses: actions/checkout@v4 - - - run: | - echo "package=$(ls -F | grep \/$ | grep -v "scripts\|examples\|tests\|config" | sed -n "s/\///g;1p")" >> $GITHUB_ENV - - - name: "Set up Python" - uses: actions/setup-python@v5 - with: - python-version-file: 'pyproject.toml' - - - name: "Cache pip" - uses: actions/cache@v4 - with: - # This path is specific to Ubuntu - path: ~/.cache/pip - # Look to see if there is a cache hit for the corresponding requirements file - key: ${{ runner.os }}-pip-${{ hashFiles('requirements*.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- - - - name: "Install dependencies" - run: | - python -m pip install --upgrade pip - if [ -f requirements-test.txt ]; then - scripts/install_requirements requirements-test.txt "${{ secrets.ADMIN_GITHUB_TOKEN }}" - elif [ -f requirements-dev.txt ]; then - scripts/install_requirements requirements-dev.txt "${{ secrets.ADMIN_GITHUB_TOKEN }}" - elif [ -f requirements.txt ]; then - scripts/install_requirements requirements.txt "${{ secrets.ADMIN_GITHUB_TOKEN }}" - fi - pip install flake8-eradicate - - if [ -d custom_components ]; then - echo '"""Stub."""' >custom_components/__init__.py - fi - - - name: "Lint with flake8 & mypy" - run: | - flake8 ${{ env.package }} tests - mypy --warn-unreachable ${{ env.package }} tests diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 124bc91..f00e169 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -17,7 +17,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Checkout the repository" - uses: "actions/checkout@v4.1.7" + uses: "actions/checkout@v4.2.2" - name: "Run hassfest validation" uses: "home-assistant/actions/hassfest@master" @@ -27,7 +27,7 @@ jobs: runs-on: "ubuntu-latest" steps: - name: "Checkout the repository" - uses: "actions/checkout@v4.1.7" + uses: "actions/checkout@v4.2.2" - name: "Run HACS validation" uses: "hacs/action@main" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 81f89e9..d437971 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,26 +9,17 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit rev: v0.6.9 hooks: + - id: ruff-format + files: ^(custom_components|bin|tests)/.+\.py$ - id: ruff args: [ --fix ] files: ^(custom_components|bin|tests)/.+\.py$ - - id: ruff-format - files: ^(custom_components|bin|tests)/.+\.py$ - repo: https://github.com/asottile/pyupgrade rev: v3.15.2 hooks: - id: pyupgrade args: [ --py312-plus ] stages: [manual] - - repo: https://github.com/pycqa/flake8.git - rev: 6.0.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-docstrings==1.5.0 - - pydocstyle==5.0.2 - files: ^(custom_components|bin|tests)/.+\.py$ - stages: [manual] - repo: https://github.com/PyCQA/bandit rev: 1.7.8 hooks: diff --git a/.ruff.toml b/.ruff.toml index 6b07ef5..ac6ca9b 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -7,7 +7,6 @@ select = [ "ALL", ] ignore = [ - "ANN101", # Missing type annotation for `self` in method "ANN401", # Dynamically typed expressions (typing.Any) are disallowed "D203", # no-blank-line-before-class (incompatible with formatter) "D212", # multi-line-summary-first-line (incompatible with formatter) @@ -27,4 +26,15 @@ keep-runtime-typing = true max-complexity = 25 [lint.per-file-ignores] -"tests/*.py" = ["ANN001", "ANN201", "ARG001", "PLR2004", "S101", "S311", "SLF001"] +"**/tests/**.py" = [ + "ANN001", # missing-type-function-argument + "ANN201", # missing-return-type-undocumented-public-function + "ARG001", # unused-function-argument + "PLR2004", # magic-value-comparison + "S101", # assert + "S311", # suspicious-non-cryptographic-random-usage + "SLF001", # private-member-access + "S105", # hardcoded-password-string + "S106", # hardcoded-password-func-arg + "S107", # hardcoded-password-default +] diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 3aa1c50..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Run Home Assistant on port 8123", - "type": "shell", - "command": "scripts/develop", - "problemMatcher": [] - } - ] -} \ No newline at end of file diff --git a/hacs.json b/hacs.json index f5b2741..9ccad91 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,7 @@ { "name": "Indoor Air Quality UK Index", "hide_default_branch": true, - "homeassistant": "2024.6.0", + "homeassistant": "2024.12.0", + "hacs": "2.0.1", "render_readme": true } diff --git a/pylintrc b/pylintrc deleted file mode 100644 index 7b6263c..0000000 --- a/pylintrc +++ /dev/null @@ -1,61 +0,0 @@ -[MASTER] -ignore=tests -# Use a conservative default here; 2 should speed up most setups and not hurt -# any too bad. Override on command line as appropriate. -jobs=2 -fail-on=I -persistent=no -extension-pkg-whitelist=ciso8601 - -[BASIC] -good-names=id,i,j,k,ex,Run,_,fp,T - -[MESSAGES CONTROL] -# Reasons disabled: -# format - handled by black -# locally-disabled - it spams too much -# duplicate-code - unavoidable -# cyclic-import - doesn't test if both import on load -# unused-argument - generic callbacks and setup methods create a lot of warnings -# too-many-* - are not enforced for the sake of readability -# too-few-* - same as too-many-* -# abstract-method - with intro of async there are always methods missing -# inconsistent-return-statements - doesn't handle raise -# too-many-ancestors - it's too strict. -# wrong-import-order - isort guards this -disable= - format, - abstract-method, - cyclic-import, - duplicate-code, - inconsistent-return-statements, - locally-disabled, - not-context-manager, - too-few-public-methods, - too-many-ancestors, - too-many-arguments, - too-many-branches, - too-many-instance-attributes, - too-many-lines, - too-many-locals, - too-many-public-methods, - too-many-return-statements, - too-many-statements, - too-many-boolean-expressions, - unused-argument, - wrong-import-order -enable= - use-symbolic-message-instead - -[REPORTS] -score=no - -[TYPECHECK] -# For attrs -ignored-classes=_CountingAttr - -[FORMAT] -expected-line-ending-format=LF - -[EXCEPTIONS] -overgeneral-exceptions=builtins.BaseException,builtins.Exception,HomeAssistantError.HomeAssistantError diff --git a/pyproject.toml b/pyproject.toml index e4f74bf..05130d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -197,6 +197,60 @@ ignore = [ "E731", # do not assign a lambda expression, use a def ] +[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" +"homeassistant.helpers.device_registry" = "dr" +"homeassistant.helpers.entity_registry" = "er" +"homeassistant.helpers.floor_registry" = "fr" +"homeassistant.helpers.issue_registry" = "ir" +"homeassistant.helpers.label_registry" = "lr" +"homeassistant.util.dt" = "dt_util" + [tool.ruff.flake8-pytest-style] fixture-parentheses = false diff --git a/requirements-dev.txt b/requirements-dev.txt index 6662323..cbca56a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,4 @@ -r requirements-test.txt -black~=24.8 -packaging~=24.1 +packaging~=24.2 pre-commit~=4.0 -PyGithub~=2.4 -pyupgrade~=3.17 -yamllint~=1.35 +PyGithub~=2.5 diff --git a/requirements-test.txt b/requirements-test.txt index 0f3b127..2d5c631 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,12 +1,8 @@ -r requirements.txt async-timeout asynctest~=0.13 -colorlog~=6.8 -flake8~=7.1 -flake8-docstrings~=1.7 -mypy~=1.11 -pylint~=3.3 -pylint-strict-informational==0.1 +colorlog~=6.9 +mypy~=1.14 pytest>=7.2 pytest-cov>=3.0 pytest-homeassistant-custom-component>=0.13 diff --git a/requirements.txt b/requirements.txt index cb20d51..0f5dba0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -homeassistant>=2024.6.0 +homeassistant>=2024.11.0 pip>=21.3.1 diff --git a/scripts/gen_releasenotes b/scripts/gen_releasenotes index f6e59c9..05e719b 100755 --- a/scripts/gen_releasenotes +++ b/scripts/gen_releasenotes @@ -1,12 +1,12 @@ #!/usr/bin/env python3 """Helper script to generate release notes.""" + import argparse -from datetime import datetime import logging import os import re import subprocess -from typing import List, Optional, Tuple +from datetime import datetime from github import Github, GithubException, Repository, Tag from packaging.version import Version @@ -66,7 +66,7 @@ def get_commits(repo: Repository, since: datetime, until: datetime): return reversed(list(commits)[:-1]) -def get_release_tags(repo: Repository) -> List[Tag.Tag]: +def get_release_tags(repo: Repository) -> list[Tag.Tag]: """Get list of all release tags from repository.""" tags = list( filter(lambda tag: is_pep440(tag.name.lstrip("v")), list(repo.get_tags())) @@ -76,7 +76,7 @@ def get_release_tags(repo: Repository) -> List[Tag.Tag]: return tags -def get_period(repo: Repository, release: Optional[str] = None) -> List[datetime]: +def get_period(repo: Repository, release: str | None = None) -> list[datetime]: """Return time period for release notes.""" data = [datetime.now()] dateformat = "%a, %d %b %Y %H:%M:%S GMT" @@ -98,7 +98,7 @@ def get_period(repo: Repository, release: Optional[str] = None) -> List[datetime return list(reversed(data[-2:])) -def gen_changes(repo: Repository, tag: Optional[str] = None) -> Tuple[str, str, str]: +def gen_changes(repo: Repository, tag: str | None = None) -> tuple[str, str, str]: """Generate list of commits.""" all_changes = "" human_changes = "" @@ -124,13 +124,15 @@ def gen_changes(repo: Repository, tag: Optional[str] = None) -> Tuple[str, str, continue change = CHANGE.format( - line=msg, link=commit.html_url, author=commit.author.login + line=msg, + link=commit.html_url, + author=commit.author.login if commit.author else "???", ) all_changes += change - if "[bot]" not in commit.author.login: - human_changes += change - else: + if commit.author and "[bot]" in commit.author.login: bot_changes += change + else: + human_changes += change return ( all_changes if all_changes != "" else NOCHANGE, diff --git a/scripts/update_requirements b/scripts/update_requirements index 5762c1f..28320d4 100755 --- a/scripts/update_requirements +++ b/scripts/update_requirements @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Helper script to update requirements.""" + import json import os import sys @@ -28,7 +29,7 @@ def get_package(requre: str) -> str: harequire = ["homeassistant"] request = requests.get( "https://raw.githubusercontent.com/home-assistant/core/dev/requirements.txt", - timeout=10 + timeout=10, ) request = request.text.split("\n") for req in request: