Compare commits

..

2 Commits

Author SHA1 Message Date
Daniel Hjelseth Høyer
a1bfc46e6c Mill connection
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2024-12-18 21:04:23 +01:00
Daniel Hjelseth Høyer
9e41f7c9ba Mill device id
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2024-12-18 20:02:02 +01:00
4649 changed files with 54212 additions and 186661 deletions

View File

@@ -62,7 +62,7 @@
"json.schemas": [
{
"fileMatch": ["homeassistant/components/*/manifest.json"],
"url": "${containerWorkspaceFolder}/script/json_schemas/manifest_schema.json"
"url": "./script/json_schemas/manifest_schema.json"
}
]
}

11
.gitattributes vendored
View File

@@ -11,14 +11,3 @@
*.pcm binary
Dockerfile.dev linguist-language=Dockerfile
# Generated files
CODEOWNERS linguist-generated=true
Dockerfile linguist-generated=true
homeassistant/generated/*.py linguist-generated=true
mypy.ini linguist-generated=true
requirements.txt linguist-generated=true
requirements_all.txt linguist-generated=true
requirements_test_all.txt linguist-generated=true
requirements_test_pre_commit.txt linguist-generated=true
script/hassfest/docker/Dockerfile linguist-generated=true

View File

@@ -32,7 +32,7 @@ jobs:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -69,7 +69,7 @@ jobs:
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
- name: Upload translations
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: translations
path: translations.tar.gz
@@ -94,7 +94,7 @@ jobs:
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v8
uses: dawidd6/action-download-artifact@v7
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend
@@ -105,7 +105,7 @@ jobs:
- name: Download nightly wheels of intents
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v8
uses: dawidd6/action-download-artifact@v7
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/intents-package
@@ -116,7 +116,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -454,7 +454,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -509,7 +509,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -522,7 +522,7 @@ jobs:
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -531,7 +531,7 @@ jobs:
- name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0
uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0
with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}

View File

@@ -40,9 +40,9 @@ env:
CACHE_VERSION: 11
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2025.2"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
HA_SHORT_VERSION: "2025.1"
DEFAULT_PYTHON: "3.12"
ALL_PYTHON_VERSIONS: "['3.12', '3.13']"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
# 10.6 is the current long-term-support
@@ -234,7 +234,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -279,7 +279,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -319,7 +319,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -359,7 +359,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -469,7 +469,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -537,7 +537,7 @@ jobs:
python --version
uv pip freeze >> pip_freeze.txt
- name: Upload pip_freeze artifact
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: pip-freeze-${{ matrix.python-version }}
path: pip_freeze.txt
@@ -572,7 +572,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -605,7 +605,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -643,7 +643,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -661,7 +661,7 @@ jobs:
. venv/bin/activate
python -m script.licenses extract --output-file=licenses-${{ matrix.python-version }}.json
- name: Upload licenses
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: licenses-${{ github.run_number }}-${{ matrix.python-version }}
path: licenses-${{ matrix.python-version }}.json
@@ -686,7 +686,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -733,7 +733,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -778,7 +778,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -859,7 +859,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -877,7 +877,7 @@ jobs:
. venv/bin/activate
python -m script.split_tests ${{ needs.info.outputs.test_group_count }} tests
- name: Upload pytest_buckets
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: pytest_buckets
path: pytest_buckets.txt
@@ -923,7 +923,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -979,14 +979,14 @@ jobs:
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
- name: Upload pytest output
if: success() || failure() && steps.pytest-full.conclusion == 'failure'
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
path: pytest-*.txt
overwrite: true
- name: Upload coverage artifact
if: needs.info.outputs.skip_coverage != 'true'
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: coverage.xml
@@ -1044,7 +1044,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1106,7 +1106,7 @@ jobs:
2>&1 | tee pytest-${{ matrix.python-version }}-${mariadb}.txt
- name: Upload pytest output
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.mariadb }}
@@ -1114,7 +1114,7 @@ jobs:
overwrite: true
- name: Upload coverage artifact
if: needs.info.outputs.skip_coverage != 'true'
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: coverage-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.mariadb }}
@@ -1173,7 +1173,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1236,7 +1236,7 @@ jobs:
2>&1 | tee pytest-${{ matrix.python-version }}-${postgresql}.txt
- name: Upload pytest output
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.postgresql }}
@@ -1244,7 +1244,7 @@ jobs:
overwrite: true
- name: Upload coverage artifact
if: needs.info.outputs.skip_coverage != 'true'
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: coverage-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.postgresql }}
@@ -1273,7 +1273,7 @@ jobs:
pattern: coverage-*
- name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'true'
uses: codecov/codecov-action@v5.3.1
uses: codecov/codecov-action@v5.1.1
with:
fail_ci_if_error: true
flags: full-suite
@@ -1319,7 +1319,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1378,14 +1378,14 @@ jobs:
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
- name: Upload pytest output
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
path: pytest-*.txt
overwrite: true
- name: Upload coverage artifact
if: needs.info.outputs.skip_coverage != 'true'
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: coverage.xml
@@ -1411,7 +1411,7 @@ jobs:
pattern: coverage-*
- name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v5.3.1
uses: codecov/codecov-action@v5.1.1
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.28.6
uses: github/codeql-action/init@v3.27.9
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.28.6
uses: github/codeql-action/analyze@v3.27.9
with:
category: "/language:python"

View File

@@ -17,7 +17,7 @@ jobs:
# - No PRs marked as no-stale
# - No issues (-1)
- name: 60 days stale PRs policy
uses: actions/stale@v9.1.0
uses: actions/stale@v9.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
@@ -57,7 +57,7 @@ jobs:
# - No issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: 90 days stale issues
uses: actions/stale@v9.1.0
uses: actions/stale@v9.0.0
with:
repo-token: ${{ steps.token.outputs.token }}
days-before-stale: 90
@@ -87,7 +87,7 @@ jobs:
# - No Issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: Needs more information stale issues policy
uses: actions/stale@v9.1.0
uses: actions/stale@v9.0.0
with:
repo-token: ${{ steps.token.outputs.token }}
only-labels: "needs-more-information"

View File

@@ -10,7 +10,7 @@ on:
- "**strings.json"
env:
DEFAULT_PYTHON: "3.13"
DEFAULT_PYTHON: "3.12"
jobs:
upload:
@@ -22,7 +22,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}

View File

@@ -17,7 +17,7 @@ on:
- "script/gen_requirements_all.py"
env:
DEFAULT_PYTHON: "3.13"
DEFAULT_PYTHON: "3.12"
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name}}
@@ -36,7 +36,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.4.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -76,37 +76,18 @@ jobs:
# Use C-Extension for SQLAlchemy
echo "REQUIRE_SQLALCHEMY_CEXT=1"
# Add additional pip wheel build constraints
echo "PIP_CONSTRAINT=build_constraints.txt"
) > .env_file
- name: Write pip wheel build constraints
run: |
(
# ninja 1.11.1.2 + 1.11.1.3 seem to be broken on at least armhf
# this caused the numpy builds to fail
# https://github.com/scikit-build/ninja-python-distributions/issues/274
echo "ninja==1.11.1.1"
) > build_constraints.txt
- name: Upload env_file
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: env_file
path: ./.env_file
include-hidden-files: true
overwrite: true
- name: Upload build_constraints
uses: actions/upload-artifact@v4.6.0
with:
name: build_constraints
path: ./build_constraints.txt
overwrite: true
- name: Upload requirements_diff
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: requirements_diff
path: ./requirements_diff.txt
@@ -118,7 +99,7 @@ jobs:
python -m script.gen_requirements_all ci
- name: Upload requirements_all_wheels
uses: actions/upload-artifact@v4.6.0
uses: actions/upload-artifact@v4.4.3
with:
name: requirements_all_wheels
path: ./requirements_all_wheels_*.txt
@@ -131,7 +112,7 @@ jobs:
strategy:
fail-fast: false
matrix:
abi: ["cp313"]
abi: ["cp312", "cp313"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
@@ -142,11 +123,6 @@ jobs:
with:
name: env_file
- name: Download build_constraints
uses: actions/download-artifact@v4.1.8
with:
name: build_constraints
- name: Download requirements_diff
uses: actions/download-artifact@v4.1.8
with:
@@ -166,7 +142,7 @@ jobs:
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "libffi-dev;openssl-dev;yaml-dev;nasm;zlib-ng-dev"
apk: "libffi-dev;openssl-dev;yaml-dev;nasm;zlib-dev"
skip-binary: aiohttp;multidict;propcache;yarl;SQLAlchemy
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
@@ -180,7 +156,7 @@ jobs:
strategy:
fail-fast: false
matrix:
abi: ["cp313"]
abi: ["cp312", "cp313"]
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
@@ -191,11 +167,6 @@ jobs:
with:
name: env_file
- name: Download build_constraints
uses: actions/download-artifact@v4.1.8
with:
name: build_constraints
- name: Download requirements_diff
uses: actions/download-artifact@v4.1.8
with:
@@ -234,7 +205,7 @@ jobs:
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-dev"
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
@@ -248,7 +219,7 @@ jobs:
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-dev"
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
@@ -262,7 +233,7 @@ jobs:
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-dev"
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.1
rev: v0.8.3
hooks:
- id: ruff
args:
@@ -12,7 +12,7 @@ repos:
hooks:
- id: codespell
args:
- --ignore-words-list=aiport,astroid,checkin,currenty,hass,iif,incomfort,lookin,nam,NotIn
- --ignore-words-list=astroid,checkin,currenty,hass,iif,incomfort,lookin,nam,NotIn
- --skip="./.*,*.csv,*.json,*.ambr"
- --quiet-level=2
exclude_types: [csv, json, html]
@@ -61,14 +61,13 @@ repos:
name: mypy
entry: script/run-in-env.sh mypy
language: script
require_serial: true
types_or: [python, pyi]
require_serial: true
files: ^(homeassistant|pylint)/.+\.(py|pyi)$
- id: pylint
name: pylint
entry: script/run-in-env.sh pylint --ignore-missing-annotations=y
entry: script/run-in-env.sh pylint -j 0 --ignore-missing-annotations=y
language: script
require_serial: true
types_or: [python, pyi]
files: ^(homeassistant|tests)/.+\.(py|pyi)$
- id: gen_requirements_all

View File

@@ -217,7 +217,6 @@ homeassistant.components.goalzero.*
homeassistant.components.google.*
homeassistant.components.google_assistant_sdk.*
homeassistant.components.google_cloud.*
homeassistant.components.google_drive.*
homeassistant.components.google_photos.*
homeassistant.components.google_sheets.*
homeassistant.components.govee_ble.*
@@ -225,10 +224,8 @@ homeassistant.components.gpsd.*
homeassistant.components.greeneye_monitor.*
homeassistant.components.group.*
homeassistant.components.guardian.*
homeassistant.components.habitica.*
homeassistant.components.hardkernel.*
homeassistant.components.hardware.*
homeassistant.components.heos.*
homeassistant.components.here_travel_time.*
homeassistant.components.history.*
homeassistant.components.history_stats.*
@@ -239,7 +236,6 @@ homeassistant.components.homeassistant_green.*
homeassistant.components.homeassistant_hardware.*
homeassistant.components.homeassistant_sky_connect.*
homeassistant.components.homeassistant_yellow.*
homeassistant.components.homee.*
homeassistant.components.homekit.*
homeassistant.components.homekit_controller
homeassistant.components.homekit_controller.alarm_control_panel
@@ -265,7 +261,6 @@ homeassistant.components.image_processing.*
homeassistant.components.image_upload.*
homeassistant.components.imap.*
homeassistant.components.imgw_pib.*
homeassistant.components.incomfort.*
homeassistant.components.input_button.*
homeassistant.components.input_select.*
homeassistant.components.input_text.*
@@ -296,7 +291,6 @@ homeassistant.components.lcn.*
homeassistant.components.ld2410_ble.*
homeassistant.components.led_ble.*
homeassistant.components.lektrico.*
homeassistant.components.letpot.*
homeassistant.components.lidarr.*
homeassistant.components.lifx.*
homeassistant.components.light.*
@@ -311,16 +305,12 @@ homeassistant.components.logbook.*
homeassistant.components.logger.*
homeassistant.components.london_underground.*
homeassistant.components.lookin.*
homeassistant.components.lovelace.*
homeassistant.components.luftdaten.*
homeassistant.components.madvr.*
homeassistant.components.manual.*
homeassistant.components.mastodon.*
homeassistant.components.matrix.*
homeassistant.components.matter.*
homeassistant.components.mcp.*
homeassistant.components.mcp_server.*
homeassistant.components.mealie.*
homeassistant.components.media_extractor.*
homeassistant.components.media_player.*
homeassistant.components.media_source.*
@@ -361,7 +351,6 @@ homeassistant.components.number.*
homeassistant.components.nut.*
homeassistant.components.onboarding.*
homeassistant.components.oncue.*
homeassistant.components.onedrive.*
homeassistant.components.onewire.*
homeassistant.components.onkyo.*
homeassistant.components.open_meteo.*
@@ -372,14 +361,10 @@ homeassistant.components.openuv.*
homeassistant.components.oralb.*
homeassistant.components.otbr.*
homeassistant.components.overkiz.*
homeassistant.components.overseerr.*
homeassistant.components.p1_monitor.*
homeassistant.components.pandora.*
homeassistant.components.panel_custom.*
homeassistant.components.peblar.*
homeassistant.components.peco.*
homeassistant.components.persistent_notification.*
homeassistant.components.person.*
homeassistant.components.pi_hole.*
homeassistant.components.ping.*
homeassistant.components.plugwise.*
@@ -393,8 +378,6 @@ homeassistant.components.pure_energie.*
homeassistant.components.purpleair.*
homeassistant.components.pushbullet.*
homeassistant.components.pvoutput.*
homeassistant.components.python_script.*
homeassistant.components.qbus.*
homeassistant.components.qnap_qsw.*
homeassistant.components.rabbitair.*
homeassistant.components.radarr.*

View File

@@ -1,5 +1,5 @@
{
// Please keep this file (mostly!) in sync with settings in home-assistant/.devcontainer/devcontainer.json
// Please keep this file in sync with settings in home-assistant/.devcontainer/devcontainer.json
// Added --no-cov to work around TypeError: message must be set
// https://github.com/microsoft/vscode-python/issues/14067
"python.testing.pytestArgs": ["--no-cov"],
@@ -12,7 +12,6 @@
"fileMatch": [
"homeassistant/components/*/manifest.json"
],
// This value differs between working with devcontainer and locally, therefor this value should NOT be in sync!
"url": "./script/json_schemas/manifest_schema.json"
}
]

View File

@@ -566,8 +566,6 @@ build.json @home-assistant/supervisor
/tests/components/google_assistant_sdk/ @tronikos
/homeassistant/components/google_cloud/ @lufton @tronikos
/tests/components/google_cloud/ @lufton @tronikos
/homeassistant/components/google_drive/ @tronikos
/tests/components/google_drive/ @tronikos
/homeassistant/components/google_generative_ai_conversation/ @tronikos
/tests/components/google_generative_ai_conversation/ @tronikos
/homeassistant/components/google_mail/ @tkdrob
@@ -580,8 +578,8 @@ build.json @home-assistant/supervisor
/tests/components/google_tasks/ @allenporter
/homeassistant/components/google_travel_time/ @eifinger
/tests/components/google_travel_time/ @eifinger
/homeassistant/components/govee_ble/ @bdraco
/tests/components/govee_ble/ @bdraco
/homeassistant/components/govee_ble/ @bdraco @PierreAronnax
/tests/components/govee_ble/ @bdraco @PierreAronnax
/homeassistant/components/govee_light_local/ @Galorhallen
/tests/components/govee_light_local/ @Galorhallen
/homeassistant/components/gpsd/ @fabaff @jrieger
@@ -639,8 +637,6 @@ build.json @home-assistant/supervisor
/tests/components/homeassistant_sky_connect/ @home-assistant/core
/homeassistant/components/homeassistant_yellow/ @home-assistant/core
/tests/components/homeassistant_yellow/ @home-assistant/core
/homeassistant/components/homee/ @Taraman17
/tests/components/homee/ @Taraman17
/homeassistant/components/homekit/ @bdraco
/tests/components/homekit/ @bdraco
/homeassistant/components/homekit_controller/ @Jc2k @bdraco
@@ -684,12 +680,12 @@ build.json @home-assistant/supervisor
/homeassistant/components/iammeter/ @lewei50
/homeassistant/components/iaqualink/ @flz
/tests/components/iaqualink/ @flz
/homeassistant/components/ibeacon/ @bdraco
/tests/components/ibeacon/ @bdraco
/homeassistant/components/icloud/ @Quentame @nzapponi
/tests/components/icloud/ @Quentame @nzapponi
/homeassistant/components/idasen_desk/ @abmantis
/tests/components/idasen_desk/ @abmantis
/homeassistant/components/igloohome/ @keithle888
/tests/components/igloohome/ @keithle888
/homeassistant/components/ign_sismologia/ @exxamalte
/tests/components/ign_sismologia/ @exxamalte
/homeassistant/components/image/ @home-assistant/core
@@ -831,8 +827,6 @@ build.json @home-assistant/supervisor
/tests/components/led_ble/ @bdraco
/homeassistant/components/lektrico/ @lektrico
/tests/components/lektrico/ @lektrico
/homeassistant/components/letpot/ @jpelgrom
/tests/components/letpot/ @jpelgrom
/homeassistant/components/lg_netcast/ @Drafteed @splinter98
/tests/components/lg_netcast/ @Drafteed @splinter98
/homeassistant/components/lg_thinq/ @LG-ThinQ-Integration
@@ -893,10 +887,6 @@ build.json @home-assistant/supervisor
/tests/components/matrix/ @PaarthShah
/homeassistant/components/matter/ @home-assistant/matter
/tests/components/matter/ @home-assistant/matter
/homeassistant/components/mcp/ @allenporter
/tests/components/mcp/ @allenporter
/homeassistant/components/mcp_server/ @allenporter
/tests/components/mcp_server/ @allenporter
/homeassistant/components/mealie/ @joostlek @andrew-codechimp
/tests/components/mealie/ @joostlek @andrew-codechimp
/homeassistant/components/meater/ @Sotolotl @emontnemery
@@ -1026,6 +1016,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/nina/ @DeerMaximum
/tests/components/nina/ @DeerMaximum
/homeassistant/components/nissan_leaf/ @filcole
/homeassistant/components/nmbs/ @thibmaek
/homeassistant/components/noaa_tides/ @jdelaney72
/homeassistant/components/nobo_hub/ @echoromeo @oyvindwe
/tests/components/nobo_hub/ @echoromeo @oyvindwe
@@ -1073,14 +1064,12 @@ build.json @home-assistant/supervisor
/tests/components/oncue/ @bdraco @peterager
/homeassistant/components/ondilo_ico/ @JeromeHXP
/tests/components/ondilo_ico/ @JeromeHXP
/homeassistant/components/onedrive/ @zweckj
/tests/components/onedrive/ @zweckj
/homeassistant/components/onewire/ @garbled1 @epenet
/tests/components/onewire/ @garbled1 @epenet
/homeassistant/components/onkyo/ @arturpragacz @eclair4151
/tests/components/onkyo/ @arturpragacz @eclair4151
/homeassistant/components/onvif/ @hunterjm @jterrace
/tests/components/onvif/ @hunterjm @jterrace
/homeassistant/components/onkyo/ @arturpragacz
/tests/components/onkyo/ @arturpragacz
/homeassistant/components/onvif/ @hunterjm
/tests/components/onvif/ @hunterjm
/homeassistant/components/open_meteo/ @frenck
/tests/components/open_meteo/ @frenck
/homeassistant/components/openai_conversation/ @balloob
@@ -1114,10 +1103,8 @@ build.json @home-assistant/supervisor
/tests/components/otbr/ @home-assistant/core
/homeassistant/components/ourgroceries/ @OnFreund
/tests/components/ourgroceries/ @OnFreund
/homeassistant/components/overkiz/ @imicknl
/tests/components/overkiz/ @imicknl
/homeassistant/components/overseerr/ @joostlek
/tests/components/overseerr/ @joostlek
/homeassistant/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117 @alexfp14
/tests/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117 @alexfp14
/homeassistant/components/ovo_energy/ @timmo001
/tests/components/ovo_energy/ @timmo001
/homeassistant/components/p1_monitor/ @klaasnicolaas
@@ -1126,8 +1113,6 @@ build.json @home-assistant/supervisor
/tests/components/palazzetti/ @dotvav
/homeassistant/components/panel_custom/ @home-assistant/frontend
/tests/components/panel_custom/ @home-assistant/frontend
/homeassistant/components/peblar/ @frenck
/tests/components/peblar/ @frenck
/homeassistant/components/peco/ @IceBotYT
/tests/components/peco/ @IceBotYT
/homeassistant/components/pegel_online/ @mib1185
@@ -1148,8 +1133,8 @@ build.json @home-assistant/supervisor
/tests/components/plaato/ @JohNan
/homeassistant/components/plex/ @jjlawren
/tests/components/plex/ @jjlawren
/homeassistant/components/plugwise/ @CoMPaTech @bouwew
/tests/components/plugwise/ @CoMPaTech @bouwew
/homeassistant/components/plugwise/ @CoMPaTech @bouwew @frenck
/tests/components/plugwise/ @CoMPaTech @bouwew @frenck
/homeassistant/components/plum_lightpad/ @ColinHarrington @prystupa
/tests/components/plum_lightpad/ @ColinHarrington @prystupa
/homeassistant/components/point/ @fredrike
@@ -1195,8 +1180,6 @@ build.json @home-assistant/supervisor
/tests/components/pyload/ @tr4nt0r
/homeassistant/components/qbittorrent/ @geoffreylagaisse @finder39
/tests/components/qbittorrent/ @geoffreylagaisse @finder39
/homeassistant/components/qbus/ @Qbus-iot @thomasddn
/tests/components/qbus/ @Qbus-iot @thomasddn
/homeassistant/components/qingping/ @bdraco
/tests/components/qingping/ @bdraco
/homeassistant/components/qld_bushfire/ @exxamalte
@@ -1273,8 +1256,8 @@ build.json @home-assistant/supervisor
/tests/components/rituals_perfume_genie/ @milanmeu @frenck
/homeassistant/components/rmvtransport/ @cgtobi
/tests/components/rmvtransport/ @cgtobi
/homeassistant/components/roborock/ @Lash-L @allenporter
/tests/components/roborock/ @Lash-L @allenporter
/homeassistant/components/roborock/ @Lash-L
/tests/components/roborock/ @Lash-L
/homeassistant/components/roku/ @ctalkington
/tests/components/roku/ @ctalkington
/homeassistant/components/romy/ @xeniter
@@ -1293,7 +1276,6 @@ build.json @home-assistant/supervisor
/tests/components/ruckus_unleashed/ @lanrat @ms264556 @gabe565
/homeassistant/components/russound_rio/ @noahhusby
/tests/components/russound_rio/ @noahhusby
/homeassistant/components/russound_rnet/ @noahhusby
/homeassistant/components/ruuvi_gateway/ @akx
/tests/components/ruuvi_gateway/ @akx
/homeassistant/components/ruuvitag_ble/ @akx
@@ -1387,8 +1369,8 @@ build.json @home-assistant/supervisor
/tests/components/slide_local/ @dontinelli
/homeassistant/components/slimproto/ @marcelveldt
/tests/components/slimproto/ @marcelveldt
/homeassistant/components/sma/ @kellerza @rklomp @erwindouna
/tests/components/sma/ @kellerza @rklomp @erwindouna
/homeassistant/components/sma/ @kellerza @rklomp
/tests/components/sma/ @kellerza @rklomp
/homeassistant/components/smappee/ @bsmappee
/tests/components/smappee/ @bsmappee
/homeassistant/components/smart_meter_texas/ @grahamwetzler
@@ -1414,8 +1396,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/solaredge_local/ @drobtravels @scheric
/homeassistant/components/solarlog/ @Ernst79 @dontinelli
/tests/components/solarlog/ @Ernst79 @dontinelli
/homeassistant/components/solax/ @squishykid @Darsstar
/tests/components/solax/ @squishykid @Darsstar
/homeassistant/components/solax/ @squishykid
/tests/components/solax/ @squishykid
/homeassistant/components/soma/ @ratsept @sebfortier2288
/tests/components/soma/ @ratsept @sebfortier2288
/homeassistant/components/sonarr/ @ctalkington
@@ -1494,8 +1476,8 @@ build.json @home-assistant/supervisor
/tests/components/system_bridge/ @timmo001
/homeassistant/components/systemmonitor/ @gjohansson-ST
/tests/components/systemmonitor/ @gjohansson-ST
/homeassistant/components/tado/ @erwindouna
/tests/components/tado/ @erwindouna
/homeassistant/components/tado/ @chiefdragon @erwindouna
/tests/components/tado/ @chiefdragon @erwindouna
/homeassistant/components/tag/ @balloob @dmulcahey
/tests/components/tag/ @balloob @dmulcahey
/homeassistant/components/tailscale/ @frenck
@@ -1589,8 +1571,8 @@ build.json @home-assistant/supervisor
/tests/components/triggercmd/ @rvmey
/homeassistant/components/tts/ @home-assistant/core
/tests/components/tts/ @home-assistant/core
/homeassistant/components/tuya/ @Tuya @zlinoliver
/tests/components/tuya/ @Tuya @zlinoliver
/homeassistant/components/tuya/ @Tuya @zlinoliver @frenck
/tests/components/tuya/ @Tuya @zlinoliver @frenck
/homeassistant/components/twentemilieu/ @frenck
/tests/components/twentemilieu/ @frenck
/homeassistant/components/twinkly/ @dr1rrb @Robbie1221 @Olen
@@ -1634,15 +1616,15 @@ build.json @home-assistant/supervisor
/tests/components/valve/ @home-assistant/core
/homeassistant/components/velbus/ @Cereal2nd @brefra
/tests/components/velbus/ @Cereal2nd @brefra
/homeassistant/components/velux/ @Julius2342 @DeerMaximum @pawlizio
/tests/components/velux/ @Julius2342 @DeerMaximum @pawlizio
/homeassistant/components/velux/ @Julius2342 @DeerMaximum
/tests/components/velux/ @Julius2342 @DeerMaximum
/homeassistant/components/venstar/ @garbled1 @jhollowe
/tests/components/venstar/ @garbled1 @jhollowe
/homeassistant/components/versasense/ @imstevenxyz
/homeassistant/components/version/ @ludeeus
/tests/components/version/ @ludeeus
/homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja @iprak
/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja @iprak
/homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja
/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja
/homeassistant/components/vicare/ @CFenner
/tests/components/vicare/ @CFenner
/homeassistant/components/vilfo/ @ManneW
@@ -1758,7 +1740,6 @@ build.json @home-assistant/supervisor
/tests/components/youless/ @gjong
/homeassistant/components/youtube/ @joostlek
/tests/components/youtube/ @joostlek
/homeassistant/components/zabbix/ @kruton
/homeassistant/components/zamg/ @killer0071234
/tests/components/zamg/ @killer0071234
/homeassistant/components/zengge/ @emontnemery

View File

@@ -13,7 +13,7 @@ ENV \
ARG QEMU_CPU
# Install uv
RUN pip3 install uv==0.5.21
RUN pip3 install uv==0.5.8
WORKDIR /usr/src
@@ -55,7 +55,7 @@ RUN \
"armv7") go2rtc_suffix='arm' ;; \
*) go2rtc_suffix=${BUILD_ARCH} ;; \
esac \
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.8/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.7/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
&& chmod +x /bin/go2rtc \
# Verify go2rtc can be executed
&& go2rtc --version

View File

@@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-homeassistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.12.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.12.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.12.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.12.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.12.0
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.12.1
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.12.1
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.12.1
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.12.1
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.12.1
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io

View File

@@ -308,7 +308,7 @@ class AuthStore:
credentials.data = data
self._async_schedule_save()
async def async_load(self) -> None:
async def async_load(self) -> None: # noqa: C901
"""Load the users."""
if self._loaded:
raise RuntimeError("Auth storage is already loaded")

View File

@@ -4,8 +4,9 @@ from __future__ import annotations
import logging
import types
from typing import Any
from typing import Any, Generic
from typing_extensions import TypeVar
import voluptuous as vol
from voluptuous.humanize import humanize_error
@@ -34,6 +35,12 @@ DATA_REQS: HassKey[set[str]] = HassKey("mfa_auth_module_reqs_processed")
_LOGGER = logging.getLogger(__name__)
_MultiFactorAuthModuleT = TypeVar(
"_MultiFactorAuthModuleT",
bound="MultiFactorAuthModule",
default="MultiFactorAuthModule",
)
class MultiFactorAuthModule:
"""Multi-factor Auth Module of validation function."""
@@ -95,9 +102,7 @@ class MultiFactorAuthModule:
raise NotImplementedError
class SetupFlow[_MultiFactorAuthModuleT: MultiFactorAuthModule = MultiFactorAuthModule](
data_entry_flow.FlowHandler
):
class SetupFlow(data_entry_flow.FlowHandler, Generic[_MultiFactorAuthModuleT]):
"""Handler for the setup flow."""
def __init__(

View File

@@ -11,7 +11,7 @@ import uuid
import attr
from attr import Attribute
from attr.setters import validate
from propcache.api import cached_property
from propcache import cached_property
from homeassistant.const import __version__
from homeassistant.data_entry_flow import FlowContext, FlowResult

View File

@@ -17,12 +17,12 @@ POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
__all__ = [
"POLICY_SCHEMA",
"AbstractPermissions",
"OwnerPermissions",
"PermissionLookup",
"PolicyPermissions",
"PolicyType",
"merge_policies",
"PermissionLookup",
"PolicyType",
"AbstractPermissions",
"PolicyPermissions",
"OwnerPermissions",
]

View File

@@ -5,8 +5,9 @@ from __future__ import annotations
from collections.abc import Mapping
import logging
import types
from typing import Any
from typing import Any, Generic
from typing_extensions import TypeVar
import voluptuous as vol
from voluptuous.humanize import humanize_error
@@ -46,6 +47,8 @@ AUTH_PROVIDER_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
_AuthProviderT = TypeVar("_AuthProviderT", bound="AuthProvider", default="AuthProvider")
class AuthProvider:
"""Provider of user authentication."""
@@ -192,8 +195,9 @@ async def load_auth_provider_module(
return module
class LoginFlow[_AuthProviderT: AuthProvider = AuthProvider](
class LoginFlow(
FlowHandler[AuthFlowContext, AuthFlowResult, tuple[str, str]],
Generic[_AuthProviderT],
):
"""Handler for the login flow."""

View File

@@ -21,7 +21,7 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.network import is_cloud_connection
from .. import InvalidAuthError

View File

@@ -18,7 +18,6 @@ import securetar
from .const import __version__ as HA_VERSION
RESTORE_BACKUP_FILE = ".HA_RESTORE"
RESTORE_BACKUP_RESULT_FILE = ".HA_RESTORE_RESULT"
KEEP_BACKUPS = ("backups",)
KEEP_DATABASE = (
"home-assistant_v2.db",
@@ -63,14 +62,8 @@ def restore_backup_file_content(config_dir: Path) -> RestoreBackupFileContent |
restore_database=instruction_content["restore_database"],
restore_homeassistant=instruction_content["restore_homeassistant"],
)
except FileNotFoundError:
except (FileNotFoundError, KeyError, json.JSONDecodeError):
return None
except (KeyError, json.JSONDecodeError) as err:
_write_restore_result_file(config_dir, False, err)
return None
finally:
# Always remove the backup instruction file to prevent a boot loop
instruction_path.unlink(missing_ok=True)
def _clear_configuration_directory(config_dir: Path, keep: Iterable[str]) -> None:
@@ -123,7 +116,7 @@ def _extract_backup(
Path(
tempdir,
"extracted",
f"homeassistant.tar{'.gz' if backup_meta['compressed'] else ''}",
f"homeassistant.tar{'.gz' if backup_meta["compressed"] else ''}",
),
gzip=backup_meta["compressed"],
key=password_to_key(restore_content.password)
@@ -146,7 +139,6 @@ def _extract_backup(
config_dir,
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(*(keep)),
ignore_dangling_symlinks=True,
)
elif restore_content.restore_database:
for entry in KEEP_DATABASE:
@@ -164,23 +156,6 @@ def _extract_backup(
)
def _write_restore_result_file(
config_dir: Path, success: bool, error: Exception | None
) -> None:
"""Write the restore result file."""
result_path = config_dir.joinpath(RESTORE_BACKUP_RESULT_FILE)
result_path.write_text(
json.dumps(
{
"success": success,
"error": str(error) if error else None,
"error_type": str(type(error).__name__) if error else None,
}
),
encoding="utf-8",
)
def restore_backup(config_dir_path: str) -> bool:
"""Restore the backup file if any.
@@ -199,14 +174,7 @@ def restore_backup(config_dir_path: str) -> bool:
restore_content=restore_content,
)
except FileNotFoundError as err:
file_not_found = ValueError(f"Backup file {backup_file_path} does not exist")
_write_restore_result_file(config_dir, False, file_not_found)
raise file_not_found from err
except Exception as err:
_write_restore_result_file(config_dir, False, err)
raise
else:
_write_restore_result_file(config_dir, True, None)
raise ValueError(f"Backup file {backup_file_path} does not exist") from err
if restore_content.remove_after_restore:
backup_file_path.unlink(missing_ok=True)
_LOGGER.info("Restore complete, restarting")

View File

@@ -31,7 +31,7 @@ def _check_import_call_allowed(mapped_args: dict[str, Any]) -> bool:
def _check_file_allowed(mapped_args: dict[str, Any]) -> bool:
# If the file is in /proc we can ignore it.
args = mapped_args["args"]
path = args[0] if type(args[0]) is str else str(args[0])
path = args[0] if type(args[0]) is str else str(args[0]) # noqa: E721
return path.startswith(ALLOWED_FILE_PREFIXES)

View File

@@ -89,7 +89,7 @@ from .helpers import (
)
from .helpers.dispatcher import async_dispatcher_send_internal
from .helpers.storage import get_internal_store_manager
from .helpers.system_info import async_get_system_info
from .helpers.system_info import async_get_system_info, is_official_image
from .helpers.typing import ConfigType
from .setup import (
# _setup_started is marked as protected to make it clear
@@ -106,17 +106,11 @@ from .util.async_ import create_eager_task
from .util.hass_dict import HassKey
from .util.logging import async_activate_log_queue_handler
from .util.package import async_get_user_site, is_docker_env, is_virtual_env
from .util.system_info import is_official_image
with contextlib.suppress(ImportError):
# Ensure anyio backend is imported to avoid it being imported in the event loop
from anyio._backends import _asyncio # noqa: F401
with contextlib.suppress(ImportError):
# httpx will import trio if it is installed which does
# blocking I/O in the event loop. We want to avoid that.
import trio # noqa: F401
if TYPE_CHECKING:
from .runner import RuntimeConfig
@@ -161,16 +155,6 @@ FRONTEND_INTEGRATIONS = {
# integrations can be removed and database migration status is
# visible in frontend
"frontend",
# Hassio is an after dependency of backup, after dependencies
# are not promoted from stage 2 to earlier stages, so we need to
# add it here. Hassio needs to be setup before backup, otherwise
# the backup integration will think we are a container/core install
# when using HAOS or Supervised install.
"hassio",
# Backup is an after dependency of frontend, after dependencies
# are not promoted from stage 2 to earlier stages, so we need to
# add it here.
"backup",
}
RECORDER_INTEGRATIONS = {
# Setup after frontend
@@ -268,7 +252,6 @@ PRELOAD_STORAGE = [
"assist_pipeline.pipelines",
"core.analytics",
"auth_module.totp",
"backup",
]

View File

@@ -5,7 +5,6 @@
"google_assistant",
"google_assistant_sdk",
"google_cloud",
"google_drive",
"google_generative_ai_conversation",
"google_mail",
"google_maps",

View File

@@ -2,7 +2,6 @@
"domain": "microsoft",
"name": "Microsoft",
"integrations": [
"azure_data_explorer",
"azure_devops",
"azure_event_hub",
"azure_service_bus",
@@ -11,7 +10,6 @@
"microsoft_face",
"microsoft",
"msteams",
"onedrive",
"xbox"
]
}

View File

@@ -34,17 +34,17 @@
"services": {
"capture_image": {
"name": "Capture image",
"description": "Requests a new image capture from a camera device.",
"description": "Request a new image capture from a camera device.",
"fields": {
"entity_id": {
"name": "Entity",
"description": "Entity ID of the camera to request an image from."
"description": "Entity id of the camera to request an image."
}
}
},
"change_setting": {
"name": "Change setting",
"description": "Changes an Abode system setting.",
"description": "Change an Abode system setting.",
"fields": {
"setting": {
"name": "Setting",
@@ -58,11 +58,11 @@
},
"trigger_automation": {
"name": "Trigger automation",
"description": "Triggers an Abode automation.",
"description": "Trigger an Abode automation.",
"fields": {
"entity_id": {
"name": "Entity",
"description": "Entity ID of the automation to trigger."
"description": "Entity id of the automation to trigger."
}
}
}

View File

@@ -26,5 +26,5 @@
"iot_class": "local_push",
"loggers": ["aioacaia"],
"quality_scale": "platinum",
"requirements": ["aioacaia==0.1.14"]
"requirements": ["aioacaia==0.1.11"]
}

View File

@@ -12,8 +12,8 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN

View File

@@ -22,7 +22,7 @@ from homeassistant.const import (
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -3,7 +3,7 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
import homeassistant.helpers.entity_registry as er
from .hub import PulseHub

View File

@@ -70,7 +70,7 @@ class PulseHub:
async def async_notify_update(self, update_type: aiopulse.UpdateType) -> None:
"""Evaluate entities when hub reports that update has occurred."""
LOGGER.debug("Hub %s updated", update_type.name)
LOGGER.debug("Hub {update_type.name} updated")
if update_type == aiopulse.UpdateType.rollers:
await update_devices(self.hass, self.config_entry, self.api.rollers)

View File

@@ -3,9 +3,9 @@
from __future__ import annotations
import logging
import telnetlib # pylint: disable=deprecated-module
from typing import Final
import telnetlib # pylint: disable=deprecated-module
import voluptuous as vol
from homeassistant.components.device_tracker import (
@@ -15,7 +15,7 @@ from homeassistant.components.device_tracker import (
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import LEASES_REGEX

View File

@@ -34,12 +34,9 @@ from .const import (
SERVICE_REMOVE_URL,
)
SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): vol.Any(cv.url, cv.path)})
SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): cv.url})
SERVICE_ADD_URL_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_URL): vol.Any(cv.url, cv.path),
}
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_URL): cv.url}
)
SERVICE_REFRESH_SCHEMA = vol.Schema(
{vol.Optional(CONF_FORCE, default=False): cv.boolean}

View File

@@ -12,7 +12,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import CONF_ADS_VAR, DATA_ADS, DOMAIN, AdsType

View File

@@ -13,7 +13,7 @@ from homeassistant.components.binary_sensor import (
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -17,7 +17,7 @@ from homeassistant.components.cover import (
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -15,7 +15,7 @@ from homeassistant.components.light import (
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -11,7 +11,7 @@ from homeassistant.components.select import (
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -15,7 +15,7 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_UNIT_OF_MEASUREMENT
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType

View File

@@ -13,7 +13,7 @@ from homeassistant.components.switch import (
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -14,7 +14,7 @@ from homeassistant.components.valve import (
)
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -66,7 +66,7 @@ class AdvantageAirZoneMotion(AdvantageAirZoneEntity, BinarySensorEntity):
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone Motion sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f"{self._zone['name']} motion"
self._attr_name = f'{self._zone["name"]} motion'
self._attr_unique_id += "-motion"
@property
@@ -84,7 +84,7 @@ class AdvantageAirZoneMyZone(AdvantageAirZoneEntity, BinarySensorEntity):
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone MyZone sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f"{self._zone['name']} myZone"
self._attr_name = f'{self._zone["name"]} myZone'
self._attr_unique_id += "-myzone"
@property

View File

@@ -103,7 +103,7 @@ class AdvantageAirZoneVent(AdvantageAirZoneEntity, SensorEntity):
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone Vent Sensor."""
super().__init__(instance, ac_key, zone_key=zone_key)
self._attr_name = f"{self._zone['name']} vent"
self._attr_name = f'{self._zone["name"]} vent'
self._attr_unique_id += "-vent"
@property
@@ -131,7 +131,7 @@ class AdvantageAirZoneSignal(AdvantageAirZoneEntity, SensorEntity):
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone wireless signal sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f"{self._zone['name']} signal"
self._attr_name = f'{self._zone["name"]} signal'
self._attr_unique_id += "-signal"
@property
@@ -165,7 +165,7 @@ class AdvantageAirZoneTemp(AdvantageAirZoneEntity, SensorEntity):
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
"""Initialize an Advantage Air Zone Temp Sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f"{self._zone['name']} temperature"
self._attr_name = f'{self._zone["name"]} temperature'
self._attr_unique_id += "-temp"
@property

View File

@@ -1,7 +1,6 @@
"""The AEMET OpenData component."""
import logging
import shutil
from aemet_opendata.exceptions import AemetError, TownNotFound
from aemet_opendata.interface import AEMET, ConnectionOptions, UpdateFeature
@@ -11,9 +10,8 @@ from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CON
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.storage import STORAGE_DIR
from .const import CONF_RADAR_UPDATES, CONF_STATION_UPDATES, DOMAIN, PLATFORMS
from .const import CONF_STATION_UPDATES, PLATFORMS
from .coordinator import AemetConfigEntry, AemetData, WeatherUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -26,15 +24,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: AemetConfigEntry) -> boo
latitude = entry.data[CONF_LATITUDE]
longitude = entry.data[CONF_LONGITUDE]
update_features: int = UpdateFeature.FORECAST
if entry.options.get(CONF_RADAR_UPDATES, False):
update_features |= UpdateFeature.RADAR
if entry.options.get(CONF_STATION_UPDATES, True):
update_features |= UpdateFeature.STATION
options = ConnectionOptions(api_key, update_features)
aemet = AEMET(aiohttp_client.async_get_clientsession(hass), options)
aemet.set_api_data_dir(hass.config.path(STORAGE_DIR, f"{DOMAIN}-{entry.unique_id}"))
try:
await aemet.select_coordinates(latitude, longitude)
except TownNotFound as err:
@@ -63,11 +57,3 @@ async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Remove a config entry."""
await hass.async_add_executor_job(
shutil.rmtree,
hass.config.path(STORAGE_DIR, f"{DOMAIN}-{entry.unique_id}"),
)

View File

@@ -17,11 +17,10 @@ from homeassistant.helpers.schema_config_entry_flow import (
SchemaOptionsFlowHandler,
)
from .const import CONF_RADAR_UPDATES, CONF_STATION_UPDATES, DEFAULT_NAME, DOMAIN
from .const import CONF_STATION_UPDATES, DEFAULT_NAME, DOMAIN
OPTIONS_SCHEMA = vol.Schema(
{
vol.Required(CONF_RADAR_UPDATES, default=False): bool,
vol.Required(CONF_STATION_UPDATES, default=True): bool,
}
)

View File

@@ -51,9 +51,8 @@ from homeassistant.components.weather import (
from homeassistant.const import Platform
ATTRIBUTION = "Powered by AEMET OpenData"
CONF_RADAR_UPDATES = "radar_updates"
CONF_STATION_UPDATES = "station_updates"
PLATFORMS = [Platform.IMAGE, Platform.SENSOR, Platform.WEATHER]
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
DEFAULT_NAME = "AEMET"
DOMAIN = "aemet"

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
from typing import Any
from aemet_opendata.const import AOD_COORDS, AOD_IMG_BYTES
from aemet_opendata.const import AOD_COORDS
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import (
@@ -26,7 +26,6 @@ TO_REDACT_CONFIG = [
TO_REDACT_COORD = [
AOD_COORDS,
AOD_IMG_BYTES,
]

View File

@@ -1,86 +0,0 @@
"""Support for the AEMET OpenData images."""
from __future__ import annotations
from typing import Final
from aemet_opendata.const import AOD_DATETIME, AOD_IMG_BYTES, AOD_IMG_TYPE, AOD_RADAR
from aemet_opendata.helpers import dict_nested_value
from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
from .entity import AemetEntity
AEMET_IMAGES: Final[tuple[ImageEntityDescription, ...]] = (
ImageEntityDescription(
key=AOD_RADAR,
translation_key="weather_radar",
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AemetConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AEMET OpenData image entities based on a config entry."""
domain_data = config_entry.runtime_data
name = domain_data.name
coordinator = domain_data.coordinator
unique_id = config_entry.unique_id
assert unique_id is not None
async_add_entities(
AemetImage(
hass,
name,
coordinator,
description,
unique_id,
)
for description in AEMET_IMAGES
if dict_nested_value(coordinator.data["lib"], [description.key]) is not None
)
class AemetImage(AemetEntity, ImageEntity):
"""Implementation of an AEMET OpenData image."""
entity_description: ImageEntityDescription
def __init__(
self,
hass: HomeAssistant,
name: str,
coordinator: WeatherUpdateCoordinator,
description: ImageEntityDescription,
unique_id: str,
) -> None:
"""Initialize the image."""
super().__init__(coordinator, name, unique_id)
ImageEntity.__init__(self, hass)
self.entity_description = description
self._attr_unique_id = f"{unique_id}-{description.key}"
self._async_update_attrs()
@callback
def _handle_coordinator_update(self) -> None:
"""Update attributes when the coordinator updates."""
self._async_update_attrs()
super()._handle_coordinator_update()
@callback
def _async_update_attrs(self) -> None:
"""Update image attributes."""
image_data = self.get_aemet_value([self.entity_description.key])
self._cached_image = Image(
content_type=image_data.get(AOD_IMG_TYPE),
content=image_data.get(AOD_IMG_BYTES),
)
self._attr_image_last_updated = image_data.get(AOD_DATETIME)

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/aemet",
"iot_class": "cloud_polling",
"loggers": ["aemet_opendata"],
"requirements": ["AEMET-OpenData==0.6.4"]
"requirements": ["AEMET-OpenData==0.6.3"]
}

View File

@@ -18,18 +18,10 @@
}
}
},
"entity": {
"image": {
"weather_radar": {
"name": "Weather radar"
}
}
},
"options": {
"step": {
"init": {
"data": {
"radar_updates": "Gather data from AEMET weather radar",
"station_updates": "Gather data from AEMET weather stations"
}
}

View File

@@ -7,7 +7,7 @@ from typing import Final
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
DOMAIN: Final = "aftership"

View File

@@ -9,7 +9,7 @@ from pyaftership import AfterShip, AfterShipException
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,

View File

@@ -18,9 +18,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry
from .const import DOMAIN
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
from .entity import AirGradientEntity
@dataclass(frozen=True, kw_only=True)
@@ -102,7 +100,6 @@ class AirGradientButton(AirGradientEntity, ButtonEntity):
self.entity_description = description
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
@exception_handler
async def async_press(self) -> None:
"""Press the button."""
await self.entity_description.press_fn(self.coordinator.client)

View File

@@ -1,6 +1,5 @@
"""Config flow for Airgradient."""
from collections.abc import Mapping
from typing import Any
from airgradient import (
@@ -12,15 +11,10 @@ from airgradient import (
from awesomeversion import AwesomeVersion
import voluptuous as vol
from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
SOURCE_USER,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.components import zeroconf
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_MODEL
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from .const import DOMAIN
@@ -43,7 +37,7 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
await self.client.set_configuration_control(ConfigurationControl.LOCAL)
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
self.data[CONF_HOST] = host = discovery_info.host
@@ -101,18 +95,10 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(
current_measures.serial_number, raise_on_progress=False
)
if self.source == SOURCE_USER:
self._abort_if_unique_id_configured()
if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch()
self._abort_if_unique_id_configured()
await self.set_configuration_source()
if self.source == SOURCE_USER:
return self.async_create_entry(
title=current_measures.model,
data={CONF_HOST: user_input[CONF_HOST]},
)
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
return self.async_create_entry(
title=current_measures.model,
data={CONF_HOST: user_input[CONF_HOST]},
)
return self.async_show_form(
@@ -120,9 +106,3 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
errors=errors,
)
async def async_step_reconfigure(
self, user_input: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reconfiguration."""
return await self.async_step_user()

View File

@@ -55,11 +55,7 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
measures = await self.client.get_current_measures()
config = await self.client.get_config()
except AirGradientError as error:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_error",
translation_placeholders={"error": str(error)},
) from error
raise UpdateFailed(error) from error
if measures.firmware_version != self._current_version:
device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get_device(

View File

@@ -1,11 +1,7 @@
"""Base class for AirGradient entities."""
from collections.abc import Callable, Coroutine
from typing import Any, Concatenate
from airgradient import get_model_name
from airgradient import AirGradientConnectionError, AirGradientError, get_model_name
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -30,31 +26,3 @@ class AirGradientEntity(CoordinatorEntity[AirGradientCoordinator]):
serial_number=coordinator.serial_number,
sw_version=measures.firmware_version,
)
def exception_handler[_EntityT: AirGradientEntity, **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate AirGradient calls to handle exceptions.
A decorator that wraps the passed in function, catches AirGradient errors.
"""
async def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
try:
await func(self, *args, **kwargs)
except AirGradientConnectionError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
translation_placeholders={"error": str(error)},
) from error
except AirGradientError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unknown_error",
translation_placeholders={"error": str(error)},
) from error
return handler

View File

@@ -19,9 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry
from .const import DOMAIN
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
from .entity import AirGradientEntity
@dataclass(frozen=True, kw_only=True)
@@ -123,7 +121,6 @@ class AirGradientNumber(AirGradientEntity, NumberEntity):
"""Return the state of the number."""
return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_set_native_value(self, value: float) -> None:
"""Set the selected value."""
await self.entity_description.set_value_fn(self.coordinator.client, int(value))

View File

@@ -29,30 +29,24 @@ rules:
unique-config-entry: done
# Silver
action-exceptions: done
action-exceptions: todo
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: No options to configure
docs-configuration-parameters: todo
docs-installation-parameters: todo
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: done
parallel-updates: todo
reauthentication-flow:
status: exempt
comment: |
This integration does not require authentication.
test-coverage: todo
test-coverage: done
# Gold
devices: done
diagnostics: done
discovery-update-info:
status: todo
comment: DHCP is still possible
discovery:
status: todo
comment: DHCP is still possible
discovery-update-info: done
discovery: done
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
@@ -68,9 +62,9 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: done
reconfiguration-flow: todo
repair-issues:
status: exempt
comment: |

View File

@@ -19,9 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry
from .const import DOMAIN, PM_STANDARD, PM_STANDARD_REVERSE
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
from .entity import AirGradientEntity
@dataclass(frozen=True, kw_only=True)
@@ -218,7 +216,6 @@ class AirGradientSelect(AirGradientEntity, SelectEntity):
"""Return the state of the select."""
return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self.entity_description.set_value_fn(self.coordinator.client, option)

View File

@@ -35,8 +35,6 @@ from .const import PM_STANDARD, PM_STANDARD_REVERSE
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class AirGradientMeasurementSensorEntityDescription(SensorEntityDescription):
@@ -139,15 +137,6 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
entity_registry_enabled_default=False,
value_fn=lambda status: status.raw_total_volatile_organic_component,
),
AirGradientMeasurementSensorEntityDescription(
key="pm02_raw",
translation_key="raw_pm02",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
value_fn=lambda status: status.raw_pm02,
),
)
CONFIG_SENSOR_TYPES: tuple[AirGradientConfigSensorEntityDescription, ...] = (

View File

@@ -17,9 +17,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unique_id_mismatch": "Please ensure you reconfigure against the same device."
"invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -121,9 +119,6 @@
"raw_nitrogen": {
"name": "Raw NOx"
},
"raw_pm02": {
"name": "Raw PM2.5"
},
"display_pm_standard": {
"name": "[%key:component::airgradient::entity::select::display_pm_standard::name%]",
"state": {
@@ -167,16 +162,5 @@
"name": "Post data to Airgradient"
}
}
},
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with the Airgradient device: {error}"
},
"unknown_error": {
"message": "An unknown error occurred while communicating with the Airgradient device: {error}"
},
"update_error": {
"message": "An error occurred while communicating with the Airgradient device: {error}"
}
}
}

View File

@@ -20,9 +20,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry
from .const import DOMAIN
from .coordinator import AirGradientCoordinator
from .entity import AirGradientEntity, exception_handler
PARALLEL_UPDATES = 1
from .entity import AirGradientEntity
@dataclass(frozen=True, kw_only=True)
@@ -101,13 +99,11 @@ class AirGradientSwitch(AirGradientEntity, SwitchEntity):
"""Return the state of the switch."""
return self.entity_description.value_fn(self.coordinator.data.config)
@exception_handler
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self.entity_description.set_value_fn(self.coordinator.client, True)
await self.coordinator.async_request_refresh()
@exception_handler
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self.entity_description.set_value_fn(self.coordinator.client, False)

View File

@@ -2,7 +2,7 @@
from datetime import timedelta
from propcache.api import cached_property
from propcache import cached_property
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
from homeassistant.core import HomeAssistant
@@ -11,7 +11,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirGradientConfigEntry, AirGradientCoordinator
from .entity import AirGradientEntity
PARALLEL_UPDATES = 1
SCAN_INTERVAL = timedelta(hours=1)

View File

@@ -13,8 +13,8 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import CONF_USE_NEAREST, DOMAIN, NO_AIRLY_SENSORS

View File

@@ -18,8 +18,8 @@ from homeassistant.config_entries import (
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN

View File

@@ -21,6 +21,7 @@ from .const import (
ATTR_API_CAT_DESCRIPTION,
ATTR_API_CAT_LEVEL,
ATTR_API_CATEGORY,
ATTR_API_PM25,
ATTR_API_POLLUTANT,
ATTR_API_REPORT_DATE,
ATTR_API_REPORT_HOUR,
@@ -90,16 +91,18 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
max_aqi_desc = obv[ATTR_API_CATEGORY][ATTR_API_CAT_DESCRIPTION]
max_aqi_poll = pollutant
# Copy Report Details
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
data[ATTR_API_REPORT_TZ] = obv[ATTR_API_REPORT_TZ]
# Copy other data from PM2.5 Value
if obv[ATTR_API_AQI_PARAM] == ATTR_API_PM25:
# Copy Report Details
data[ATTR_API_REPORT_DATE] = obv[ATTR_API_REPORT_DATE]
data[ATTR_API_REPORT_HOUR] = obv[ATTR_API_REPORT_HOUR]
data[ATTR_API_REPORT_TZ] = obv[ATTR_API_REPORT_TZ]
# Copy Station Details
data[ATTR_API_STATE] = obv[ATTR_API_STATE]
data[ATTR_API_STATION] = obv[ATTR_API_STATION]
data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE]
data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE]
# Copy Station Details
data[ATTR_API_STATE] = obv[ATTR_API_STATE]
data[ATTR_API_STATION] = obv[ATTR_API_STATION]
data[ATTR_API_STATION_LATITUDE] = obv[ATTR_API_STATION_LATITUDE]
data[ATTR_API_STATION_LONGITUDE] = obv[ATTR_API_STATION_LONGITUDE]
# Store Overall AQI
data[ATTR_API_AQI] = max_aqi

View File

@@ -39,54 +39,45 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="temp",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
"humidity": SensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
"pressure": SensorEntityDescription(
key="pressure",
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT,
),
"battery": SensorEntityDescription(
key="battery",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
),
"co2": SensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
"voc": SensorEntityDescription(
key="voc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
),
"light": SensorEntityDescription(
key="light",
native_unit_of_measurement=PERCENTAGE,
translation_key="light",
state_class=SensorStateClass.MEASUREMENT,
),
"virusRisk": SensorEntityDescription(
key="virusRisk",
translation_key="virus_risk",
state_class=SensorStateClass.MEASUREMENT,
),
"mold": SensorEntityDescription(
key="mold",
translation_key="mold",
state_class=SensorStateClass.MEASUREMENT,
),
"rssi": SensorEntityDescription(
key="rssi",
@@ -94,19 +85,16 @@ SENSORS: dict[str, SensorEntityDescription] = {
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
),
"pm1": SensorEntityDescription(
key="pm1",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
),
"pm25": SensorEntityDescription(
key="pm25",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
}
@@ -155,7 +143,8 @@ class AirthingsHeaterEnergySensor(
self._id = airthings_device.device_id
self._attr_device_info = DeviceInfo(
configuration_url=(
f"https://dashboard.airthings.com/devices/{airthings_device.device_id}"
"https://dashboard.airthings.com/devices/"
f"{airthings_device.device_id}"
),
identifiers={(DOMAIN, airthings_device.device_id)},
name=airthings_device.name,

View File

@@ -144,7 +144,7 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=discovery.name, data={})
current_addresses = self._async_current_ids(include_ignore=False)
current_addresses = self._async_current_ids()
for discovery_info in async_discovered_service_info(self.hass):
address = discovery_info.address
if address in current_addresses or address in self._discovered_devices:

View File

@@ -67,21 +67,18 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
"humidity": SensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
"pressure": SensorEntityDescription(
key="pressure",
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
"battery": SensorEntityDescription(
key="battery",
@@ -89,28 +86,24 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=0,
),
"co2": SensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"voc": SensorEntityDescription(
key="voc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"illuminance": SensorEntityDescription(
key="illuminance",
translation_key="illuminance",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
}

View File

@@ -50,7 +50,7 @@ SENSOR_DESCRIPTIONS = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: int(
history.get(
f"Outdoor {'AQI(US)' if settings['is_aqi_usa'] else 'AQI(CN)'}", -1
f'Outdoor {"AQI(US)" if settings["is_aqi_usa"] else "AQI(CN)"}', -1
)
),
translation_key="outdoor_air_quality_index",

View File

@@ -5,14 +5,7 @@ from __future__ import annotations
import logging
from typing import Any
from aioairzone.const import (
AZD_FIRMWARE,
AZD_FULL_NAME,
AZD_MAC,
AZD_MODEL,
AZD_WEBSERVER,
DEFAULT_SYSTEM_ID,
)
from aioairzone.const import AZD_MAC, AZD_WEBSERVER, DEFAULT_SYSTEM_ID
from aioairzone.localapi import AirzoneLocalApi, ConnectionOptions
from homeassistant.config_entries import ConfigEntry
@@ -24,7 +17,6 @@ from homeassistant.helpers import (
entity_registry as er,
)
from .const import DOMAIN, MANUFACTURER
from .coordinator import AirzoneUpdateCoordinator
PLATFORMS: list[Platform] = [
@@ -86,7 +78,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> b
options = ConnectionOptions(
entry.data[CONF_HOST],
entry.data[CONF_PORT],
entry.data[CONF_ID],
entry.data.get(CONF_ID, DEFAULT_SYSTEM_ID),
)
airzone = AirzoneLocalApi(aiohttp_client.async_get_clientsession(hass), options)
@@ -96,22 +88,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> b
entry.runtime_data = coordinator
device_registry = dr.async_get(hass)
ws_data: dict[str, Any] | None = coordinator.data.get(AZD_WEBSERVER)
if ws_data is not None:
mac = ws_data.get(AZD_MAC, "")
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, mac)},
identifiers={(DOMAIN, f"{entry.entry_id}_ws")},
manufacturer=MANUFACTURER,
model=ws_data.get(AZD_MODEL),
name=ws_data.get(AZD_FULL_NAME),
sw_version=ws_data.get(AZD_FIRMWARE),
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@@ -120,25 +96,3 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> b
async def async_unload_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> bool:
"""Migrate an old entry."""
if entry.version == 1 and entry.minor_version < 2:
# Add missing CONF_ID
system_id = entry.data.get(CONF_ID, DEFAULT_SYSTEM_ID)
new_data = entry.data.copy()
new_data[CONF_ID] = system_id
hass.config_entries.async_update_entry(
entry,
data=new_data,
minor_version=2,
)
_LOGGER.info(
"Migration to configuration version %s.%s successful",
entry.version,
entry.minor_version,
)
return True

View File

@@ -10,12 +10,12 @@ from aioairzone.exceptions import AirzoneError, InvalidSystem
from aioairzone.localapi import AirzoneLocalApi, ConnectionOptions
import voluptuous as vol
from homeassistant.components import dhcp
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_ID, CONF_PORT
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from .const import DOMAIN
@@ -44,7 +44,6 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
_discovered_ip: str | None = None
_discovered_mac: str | None = None
MINOR_VERSION = 2
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -54,9 +53,6 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
if CONF_ID not in user_input:
user_input[CONF_ID] = DEFAULT_SYSTEM_ID
self._async_abort_entries_match(user_input)
airzone = AirzoneLocalApi(
@@ -64,7 +60,7 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
ConnectionOptions(
user_input[CONF_HOST],
user_input[CONF_PORT],
user_input[CONF_ID],
user_input.get(CONF_ID, DEFAULT_SYSTEM_ID),
),
)
@@ -88,9 +84,6 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
)
title = f"Airzone {user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
if user_input[CONF_ID] != DEFAULT_SYSTEM_ID:
title += f" #{user_input[CONF_ID]}"
return self.async_create_entry(title=title, data=user_input)
return self.async_show_form(
@@ -100,7 +93,7 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
self, discovery_info: dhcp.DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle DHCP discovery."""
self._discovered_ip = discovery_info.ip

View File

@@ -68,9 +68,8 @@ class AirzoneSystemEntity(AirzoneEntity):
model=self.get_airzone_value(AZD_MODEL),
name=f"System {self.system_id}",
sw_version=self.get_airzone_value(AZD_FIRMWARE),
via_device=(DOMAIN, f"{entry.entry_id}_ws"),
)
if AZD_WEBSERVER in self.coordinator.data:
self._attr_device_info["via_device"] = (DOMAIN, f"{entry.entry_id}_ws")
self._attr_unique_id = entry.unique_id or entry.entry_id
@property
@@ -103,9 +102,8 @@ class AirzoneHotWaterEntity(AirzoneEntity):
manufacturer=MANUFACTURER,
model="DHW",
name=self.get_airzone_value(AZD_NAME),
via_device=(DOMAIN, f"{entry.entry_id}_ws"),
)
if AZD_WEBSERVER in self.coordinator.data:
self._attr_device_info["via_device"] = (DOMAIN, f"{entry.entry_id}_ws")
self._attr_unique_id = entry.unique_id or entry.entry_id
def get_airzone_value(self, key: str) -> Any:

View File

@@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone",
"iot_class": "local_polling",
"loggers": ["aioairzone"],
"requirements": ["aioairzone==0.9.9"]
"requirements": ["aioairzone==0.9.7"]
}

View File

@@ -7,7 +7,7 @@ from datetime import timedelta
import logging
from typing import TYPE_CHECKING, Any, Final, final
from propcache.api import cached_property
from propcache import cached_property
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
@@ -24,7 +24,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import make_entity_service_schema
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent

View File

@@ -23,7 +23,8 @@ from homeassistant.const import (
SERVICE_ALARM_TRIGGER,
)
from homeassistant.core import Context, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers import entity_registry as er
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import get_supported_features
from homeassistant.helpers.typing import ConfigType, TemplateVarsType

View File

@@ -12,7 +12,8 @@ from homeassistant.components.alarm_control_panel import (
)
from homeassistant.const import ATTR_CODE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers import entity_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
STATE_ON,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType

View File

@@ -50,7 +50,8 @@ from homeassistant.const import (
UnitOfVolume,
)
from homeassistant.core import HomeAssistant, State
from homeassistant.util import color as color_util, dt as dt_util
import homeassistant.util.color as color_util
import homeassistant.util.dt as dt_util
from .const import (
API_TEMP_UNITS,

View File

@@ -474,30 +474,25 @@ class ClimateCapabilities(AlexaEntity):
# If we support two modes, one being off, we allow turning on too.
supported_features = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (
(
self.entity.domain == climate.DOMAIN
and climate.HVACMode.OFF
in (self.entity.attributes.get(climate.ATTR_HVAC_MODES) or [])
)
or (
self.entity.domain == climate.DOMAIN
and (
supported_features
& (
climate.ClimateEntityFeature.TURN_ON
| climate.ClimateEntityFeature.TURN_OFF
)
self.entity.domain == climate.DOMAIN
and climate.HVACMode.OFF
in (self.entity.attributes.get(climate.ATTR_HVAC_MODES) or [])
or self.entity.domain == climate.DOMAIN
and (
supported_features
& (
climate.ClimateEntityFeature.TURN_ON
| climate.ClimateEntityFeature.TURN_OFF
)
)
or (
self.entity.domain == water_heater.DOMAIN
and (supported_features & water_heater.WaterHeaterEntityFeature.ON_OFF)
)
or self.entity.domain == water_heater.DOMAIN
and (supported_features & water_heater.WaterHeaterEntityFeature.ON_OFF)
):
yield AlexaPowerController(self.entity)
if self.entity.domain == climate.DOMAIN or (
self.entity.domain == water_heater.DOMAIN
if (
self.entity.domain == climate.DOMAIN
or self.entity.domain == water_heater.DOMAIN
and (
supported_features
& water_heater.WaterHeaterEntityFeature.OPERATION_MODE

View File

@@ -12,7 +12,7 @@ from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import template
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
import homeassistant.util.dt as dt_util
from .const import (
API_PASSWORD,

View File

@@ -24,7 +24,7 @@ from homeassistant.core import (
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.significant_change import create_checker
from homeassistant.util import dt as dt_util
import homeassistant.util.dt as dt_util
from homeassistant.util.json import JsonObjectType, json_loads_object
from .const import (
@@ -317,8 +317,9 @@ async def async_enable_proactive_mode(
if should_doorbell:
old_state = data["old_state"]
if new_state.domain == event.DOMAIN or (
new_state.state == STATE_ON
if (
new_state.domain == event.DOMAIN
or new_state.state == STATE_ON
and (old_state is None or old_state.state != STATE_ON)
):
await async_send_doorbell_event_message(

View File

@@ -16,7 +16,7 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import CONF_API_KEY, CONF_CURRENCY, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

View File

@@ -22,7 +22,7 @@ from homeassistant.generated.amazon_polly import (
SUPPORTED_REGIONS,
SUPPORTED_VOICES,
)
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import (

View File

@@ -17,8 +17,9 @@ from homeassistant.const import (
)
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.dispatcher import async_dispatcher_send
import homeassistant.helpers.entity_registry as er
from .const import (
ATTR_LAST_DATA,

View File

@@ -37,7 +37,8 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import Unauthorized, UnknownUser
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.service import async_extract_entity_ids

View File

@@ -41,7 +41,7 @@
}
},
"enable_motion_recording": {
"name": "Enable motion recording",
"name": "Enables motion recording",
"description": "Enables recording a clip to camera storage when motion is detected.",
"fields": {
"entity_id": {
@@ -51,8 +51,8 @@
}
},
"disable_motion_recording": {
"name": "Disable motion recording",
"description": "Disables recording a clip to camera storage when motion is detected.",
"name": "Disables motion recording",
"description": "Disable recording a clip to camera storage when motion is detected.",
"fields": {
"entity_id": {
"name": "[%key:component::amcrest::services::enable_recording::fields::entity_id::name%]",

View File

@@ -14,8 +14,8 @@ from homeassistant.components.air_quality import (
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle

View File

@@ -7,7 +7,7 @@ import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import Event, HassJob, HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_call_later, async_track_time_interval
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey

View File

@@ -11,7 +11,6 @@ import uuid
import aiohttp
from homeassistant import config as conf_util
from homeassistant.components import hassio
from homeassistant.components.api import ATTR_INSTALLATION_TYPE
from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN
@@ -23,12 +22,13 @@ from homeassistant.components.recorder import (
DOMAIN as RECORDER_DOMAIN,
get_instance as get_recorder_instance,
)
import homeassistant.config as conf_util
from homeassistant.config_entries import SOURCE_IGNORE
from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.entity_registry as er
from homeassistant.helpers.hassio import is_hassio
from homeassistant.helpers.storage import Store
from homeassistant.helpers.system_info import async_get_system_info

View File

@@ -11,7 +11,12 @@ from python_homeassistant_analytics import (
from python_homeassistant_analytics.models import IntegrationType
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
@@ -20,7 +25,6 @@ from homeassistant.helpers.selector import (
SelectSelectorConfig,
)
from . import AnalyticsInsightsConfigEntry
from .const import (
CONF_TRACKED_ADDONS,
CONF_TRACKED_CUSTOM_INTEGRATIONS,
@@ -42,7 +46,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
@staticmethod
@callback
def async_get_options_flow(
config_entry: AnalyticsInsightsConfigEntry,
config_entry: ConfigEntry,
) -> HomeassistantAnalyticsOptionsFlowHandler:
"""Get the options flow for this handler."""
return HomeassistantAnalyticsOptionsFlowHandler()

View File

@@ -7,6 +7,6 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["python_homeassistant_analytics"],
"requirements": ["python-homeassistant-analytics==0.8.1"],
"requirements": ["python-homeassistant-analytics==0.8.0"],
"single_config_entry": true
}

View File

@@ -1,100 +0,0 @@
rules:
# Bronze
action-setup:
status: exempt
comment: |
This integration does not provide additional actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
This integration does not provide additional actions.
docs-high-level-description: todo
docs-installation-instructions: todo
docs-removal-instructions: todo
entity-event-setup:
status: exempt
comment: |
Entities of this integration does not explicitly subscribe to events.
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: |
This integration does not provide actions.
config-entry-unloading: done
docs-configuration-parameters: todo
docs-installation-parameters: todo
entity-unavailable:
status: done
comment: |
The coordinator handles this.
integration-owner: done
log-when-unavailable:
status: done
comment: |
The coordinator handles this.
parallel-updates: todo
reauthentication-flow:
status: exempt
comment: |
This integration does not require authentication.
test-coverage: todo
# Gold
devices: done
diagnostics: todo
discovery-update-info:
status: exempt
comment: |
This integration is a cloud service and thus does not support discovery.
discovery:
status: exempt
comment: |
This integration is a cloud service and thus does not support discovery.
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: todo
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: todo
dynamic-devices:
status: exempt
comment: |
This integration has a fixed single service.
entity-category: done
entity-device-class:
status: exempt
comment: |
This integration does not have entities with device classes.
entity-disabled-by-default: done
entity-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow:
status: exempt
comment: All the options of this integration are managed via the options flow
repair-issues:
status: exempt
comment: |
This integration doesn't have any cases where raising an issue is needed.
stale-devices:
status: exempt
comment: |
This integration has a fixed single service.
# Platinum
async-dependency: done
inject-websession: done
strict-typing: done

View File

@@ -21,7 +21,7 @@
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"invalid_unique_id": "Impossible to determine a valid unique ID for the device"
"invalid_unique_id": "Impossible to determine a valid unique id for the device"
}
},
"options": {
@@ -38,17 +38,17 @@
}
},
"apps": {
"title": "Configure Android apps",
"description": "Configure application ID {app_id}",
"title": "Configure Android Apps",
"description": "Configure application id {app_id}",
"data": {
"app_name": "Application name",
"app_name": "Application Name",
"app_id": "Application ID",
"app_delete": "Check to delete this application"
}
},
"rules": {
"title": "Configure Android state detection rules",
"description": "Configure detection rule for application ID {rule_id}",
"description": "Configure detection rule for application id {rule_id}",
"data": {
"rule_id": "[%key:component::androidtv::options::step::apps::data::app_id%]",
"rule_values": "List of state detection rules (see documentation)",

View File

@@ -14,6 +14,7 @@ from androidtvremote2 import (
)
import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigEntry,
@@ -30,7 +31,6 @@ from homeassistant.helpers.selector import (
SelectSelectorConfig,
SelectSelectorMode,
)
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from .const import CONF_APP_ICON, CONF_APP_NAME, CONF_APPS, CONF_ENABLE_IME, DOMAIN
from .helpers import create_api, get_enable_ime
@@ -142,7 +142,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
_LOGGER.debug("Android TV device found via zeroconf: %s", discovery_info)
@@ -156,12 +156,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
# and one of them, which could end up being in discovery_info.host, is from a
# different device. If any of the discovery_info.ip_addresses matches the
# existing host, don't update the host.
if (
existing_config_entry
# Ignored entries don't have host
and CONF_HOST in existing_config_entry.data
and len(discovery_info.ip_addresses) > 1
):
if existing_config_entry and len(discovery_info.ip_addresses) > 1:
existing_host = existing_config_entry.data[CONF_HOST]
if existing_host != self.host:
if existing_host in [

View File

@@ -44,12 +44,12 @@
}
},
"apps": {
"title": "Configure Android apps",
"description": "Configure application ID {app_id}",
"title": "Configure Android Apps",
"description": "Configure application id {app_id}",
"data": {
"app_name": "Application name",
"app_name": "Application Name",
"app_id": "Application ID",
"app_icon": "Application icon",
"app_icon": "Application Icon",
"app_delete": "Check to delete this application"
}
}

View File

@@ -15,7 +15,7 @@ from homeassistant.components.switch import (
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle

View File

@@ -12,7 +12,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_MODEL, CONF_PORT
from homeassistant.helpers import config_validation as cv
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import format_mac
from .const import DEFAULT_NAME, DEFAULT_PORT, DEVICE_TIMEOUT_SECONDS, DOMAIN

View File

@@ -27,7 +27,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, TemplateError
from homeassistant.helpers import device_registry as dr, intent, llm, template
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import ulid as ulid_util
from homeassistant.util import ulid
from . import AnthropicConfigEntry
from .const import (
@@ -164,7 +164,7 @@ class AnthropicConversationEntity(
]
if user_input.conversation_id is None:
conversation_id = ulid_util.ulid_now()
conversation_id = ulid.ulid_now()
messages = []
elif user_input.conversation_id in self.history:
@@ -177,8 +177,8 @@ class AnthropicConversationEntity(
# a new conversation was started. If the user picks their own, they
# want to track a conversation and we respect it.
try:
ulid_util.ulid_to_bytes(user_input.conversation_id)
conversation_id = ulid_util.ulid_now()
ulid.ulid_to_bytes(user_input.conversation_id)
conversation_id = ulid.ulid_now()
except ValueError:
conversation_id = user_input.conversation_id

View File

@@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/anthropic",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["anthropic==0.44.0"]
"requirements": ["anthropic==0.31.2"]
}

Some files were not shown because too many files have changed in this diff Show More