mirror of
https://github.com/home-assistant/core.git
synced 2026-05-23 17:25:10 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01db441a13 |
@@ -1,52 +0,0 @@
|
||||
name: Cache and install APT packages
|
||||
description: >-
|
||||
Wraps awalsh128/cache-apt-pkgs-action with the workarounds Home Assistant CI
|
||||
needs. Removes the conflicting Microsoft apt source before any apt run, and
|
||||
points the dynamic linker at the host's multiarch lib subdirectories so
|
||||
shared libraries that rely on update-alternatives or postinst-managed paths
|
||||
(eg libblas, liblapack pulled in by ffmpeg) stay reachable since the upstream
|
||||
action does not execute postinst scripts on cache restore.
|
||||
|
||||
inputs:
|
||||
packages:
|
||||
description: Space-delimited list of apt packages to install.
|
||||
required: true
|
||||
version:
|
||||
description: Cache version. Bump to invalidate the cache.
|
||||
required: false
|
||||
default: "1"
|
||||
execute_install_scripts:
|
||||
description: >-
|
||||
Pass-through to awalsh128/cache-apt-pkgs-action. Postinst scripts are not
|
||||
actually cached by the upstream action, so this is largely a no-op today.
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Remove conflicting Microsoft apt source
|
||||
shell: bash
|
||||
run: sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list
|
||||
- name: Install apt packages via cache
|
||||
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
|
||||
with:
|
||||
packages: ${{ inputs.packages }}
|
||||
version: ${{ inputs.version }}
|
||||
execute_install_scripts: ${{ inputs.execute_install_scripts }}
|
||||
- name: Refresh dynamic linker cache
|
||||
shell: bash
|
||||
run: |
|
||||
# awalsh128/cache-apt-pkgs-action does not run postinst scripts on
|
||||
# cache restore, so update-alternatives symlinks (eg the one libblas
|
||||
# creates at /usr/lib/<multiarch>/libblas.so.3) are never produced.
|
||||
# Add every /usr/lib/<multiarch> subdirectory that holds shared
|
||||
# libraries to the ldconfig search path so the dynamic linker still
|
||||
# finds them. Use dpkg-architecture to derive the host's multiarch
|
||||
# tuple so this works on non-x86_64 runners too.
|
||||
multiarch="$(dpkg-architecture -qDEB_HOST_MULTIARCH)"
|
||||
find "/usr/lib/${multiarch}" -mindepth 2 -maxdepth 2 \
|
||||
-name '*.so.*' -printf '%h\n' \
|
||||
| sort -u \
|
||||
| sudo tee /etc/ld.so.conf.d/zzz-cache-apt-extras.conf > /dev/null
|
||||
sudo ldconfig
|
||||
@@ -1,42 +0,0 @@
|
||||
name: Set up uv and managed Python
|
||||
description: >-
|
||||
Pins uv (avoids the raw.githubusercontent.com manifest fetch on cache miss)
|
||||
and proactively installs the requested Python so cached venvs created with
|
||||
`uv venv` resolve their interpreter symlinks in jobs that only restore the
|
||||
venv. setup-uv alone only sets UV_PYTHON, it does not actually fetch the
|
||||
interpreter until uv first uses it, so jobs that just activate the venv
|
||||
blow up with broken symlinks on cache hit.
|
||||
|
||||
inputs:
|
||||
python-version:
|
||||
description: The Python version uv should install and use.
|
||||
required: true
|
||||
uv-version:
|
||||
description: The uv version setup-uv should install.
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
python-version:
|
||||
description: The Python version uv reports as installed.
|
||||
value: ${{ steps.uv.outputs.python-version }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set up uv
|
||||
id: uv
|
||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||
with:
|
||||
version: ${{ inputs.uv-version }}
|
||||
python-version: ${{ inputs.python-version }}
|
||||
# Persist astral's managed Python across jobs so 'uv venv' below is
|
||||
# fast on the second job onwards.
|
||||
cache-python: true
|
||||
# Lint-only and codegen jobs touch no Python deps, so the post-step
|
||||
# cache save would otherwise abort the job.
|
||||
ignore-nothing-to-cache: true
|
||||
- name: Install Python interpreter
|
||||
shell: bash
|
||||
env:
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
run: uv python install "${PYTHON_VERSION}"
|
||||
+241
-133
@@ -37,7 +37,7 @@ on:
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 4
|
||||
CACHE_VERSION: 3
|
||||
MYPY_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: "2026.6"
|
||||
ADDITIONAL_PYTHON_VERSIONS: "[]"
|
||||
@@ -60,7 +60,9 @@ env:
|
||||
# - 15.2 is the latest (as of 9 Feb 2023)
|
||||
POSTGRESQL_VERSIONS: "['postgres:12.14','postgres:15.2']"
|
||||
UV_CACHE_DIR: /tmp/uv-cache
|
||||
APT_CACHE_VERSION: 1
|
||||
APT_CACHE_BASE: /home/runner/work/apt
|
||||
APT_CACHE_DIR: /home/runner/work/apt/cache
|
||||
APT_LIST_CACHE_DIR: /home/runner/work/apt/lists
|
||||
SQLALCHEMY_WARN_20: 1
|
||||
PYTHONASYNCIODEBUG: 1
|
||||
HASS_CI: 1
|
||||
@@ -84,13 +86,12 @@ jobs:
|
||||
core: ${{ steps.core.outputs.changes }}
|
||||
integrations_glob: ${{ steps.info.outputs.integrations_glob }}
|
||||
integrations: ${{ steps.integrations.outputs.changes }}
|
||||
apt_cache_key: ${{ steps.generate_apt_cache_key.outputs.key }}
|
||||
python_cache_key: ${{ steps.generate_python_cache_key.outputs.key }}
|
||||
requirements: ${{ steps.core.outputs.requirements }}
|
||||
mariadb_groups: ${{ steps.info.outputs.mariadb_groups }}
|
||||
postgresql_groups: ${{ steps.info.outputs.postgresql_groups }}
|
||||
python_versions: ${{ steps.info.outputs.python_versions }}
|
||||
default_python: ${{ steps.info.outputs.default_python }}
|
||||
uv_version: ${{ steps.info.outputs.uv_version }}
|
||||
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
|
||||
test_group_count: ${{ steps.info.outputs.test_group_count }}
|
||||
test_groups: ${{ steps.info.outputs.test_groups }}
|
||||
@@ -115,6 +116,10 @@ jobs:
|
||||
# Include HA_SHORT_VERSION to force the immediate creation
|
||||
# of a new uv cache entry after a version bump.
|
||||
echo "key=venv-${CACHE_VERSION}-${HA_SHORT_VERSION}-${HASH_REQUIREMENTS_TEST}-${HASH_REQUIREMENTS}-${HASH_REQUIREMENTS_ALL}-${HASH_PACKAGE_CONSTRAINTS}-${HASH_GEN_REQUIREMENTS}" >> $GITHUB_OUTPUT
|
||||
- name: Generate partial apt restore key
|
||||
id: generate_apt_cache_key
|
||||
run: |
|
||||
echo "key=$(lsb_release -rs)-apt-${CACHE_VERSION}-${HA_SHORT_VERSION}" >> $GITHUB_OUTPUT
|
||||
- name: Filter for core changes
|
||||
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
id: core
|
||||
@@ -237,11 +242,6 @@ jobs:
|
||||
echo "postgresql_groups=${postgresql_groups}" >> $GITHUB_OUTPUT
|
||||
echo "python_versions: ${all_python_versions}"
|
||||
echo "python_versions=${all_python_versions}" >> $GITHUB_OUTPUT
|
||||
echo "default_python: ${default_python}"
|
||||
echo "default_python=${default_python}" >> $GITHUB_OUTPUT
|
||||
uv_version=$(grep '^uv==' requirements.txt | cut -d'=' -f3)
|
||||
echo "uv_version: ${uv_version}"
|
||||
echo "uv_version=${uv_version}" >> $GITHUB_OUTPUT
|
||||
echo "test_full_suite: ${test_full_suite}"
|
||||
echo "test_full_suite=${test_full_suite}" >> $GITHUB_OUTPUT
|
||||
echo "integrations_glob: ${integrations_glob}"
|
||||
@@ -351,12 +351,12 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up uv and Python ${{ matrix.python-version }}
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -384,41 +384,80 @@ jobs:
|
||||
path: ${{ env.UV_CACHE_DIR }}
|
||||
key: ${{ steps.generate-uv-key.outputs.full_key }}
|
||||
restore-keys: ${{ steps.generate-uv-key.outputs.partial_key }}
|
||||
- name: Install additional OS dependencies
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/cache-apt-packages
|
||||
- name: Check if apt cache exists
|
||||
id: cache-apt-check
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
packages: >-
|
||||
bluez
|
||||
ffmpeg
|
||||
libturbojpeg
|
||||
libxml2-utils
|
||||
libavcodec-dev
|
||||
libavdevice-dev
|
||||
libavfilter-dev
|
||||
libavformat-dev
|
||||
libavutil-dev
|
||||
libswresample-dev
|
||||
libswscale-dev
|
||||
lookup-only: ${{ steps.cache-venv.outputs.cache-hit == 'true' }}
|
||||
path: |
|
||||
${{ env.APT_CACHE_DIR }}
|
||||
${{ env.APT_LIST_CACHE_DIR }}
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
|
||||
- name: Install additional OS dependencies
|
||||
if: |
|
||||
steps.cache-venv.outputs.cache-hit != 'true'
|
||||
|| steps.cache-apt-check.outputs.cache-hit != 'true'
|
||||
id: install-os-deps
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
APT_CACHE_HIT: ${{ steps.cache-apt-check.outputs.cache-hit }}
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
if [[ "${APT_CACHE_HIT}" != 'true' ]]; then
|
||||
mkdir -p ${APT_CACHE_DIR}
|
||||
mkdir -p ${APT_LIST_CACHE_DIR}
|
||||
fi
|
||||
|
||||
sudo apt-get update \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR}
|
||||
sudo apt-get -y install \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR} \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libxml2-utils \
|
||||
libavcodec-dev \
|
||||
libavdevice-dev \
|
||||
libavfilter-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libswresample-dev \
|
||||
libswscale-dev \
|
||||
libudev-dev
|
||||
version: ${{ env.APT_CACHE_VERSION }}
|
||||
execute_install_scripts: true
|
||||
|
||||
if [[ "${APT_CACHE_HIT}" != 'true' ]]; then
|
||||
sudo chmod -R 755 ${APT_CACHE_BASE}
|
||||
fi
|
||||
- name: Save apt cache
|
||||
if: |
|
||||
always()
|
||||
&& steps.cache-apt-check.outputs.cache-hit != 'true'
|
||||
&& steps.install-os-deps.outcome == 'success'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.APT_CACHE_DIR }}
|
||||
${{ env.APT_LIST_CACHE_DIR }}
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
|
||||
- name: Create Python virtual environment
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
id: create-venv
|
||||
env:
|
||||
PYTHON_VERSION: ${{ steps.python.outputs.python-version }}
|
||||
run: |
|
||||
uv venv venv --python "${PYTHON_VERSION}"
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pip install "$(grep '^uv' < requirements.txt)"
|
||||
uv pip install -U "pip>=25.2"
|
||||
uv pip install -r requirements.txt
|
||||
uv pip install -r requirements_all.txt -r requirements_test.txt
|
||||
uv pip install -e . --config-settings editable_mode=compat
|
||||
- name: Dump pip freeze
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
uv pip freeze >> pip_freeze.txt
|
||||
@@ -467,22 +506,36 @@ jobs:
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
steps:
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.APT_CACHE_DIR }}
|
||||
${{ env.APT_LIST_CACHE_DIR }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get update \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR}
|
||||
sudo apt-get -y install \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR} \
|
||||
libturbojpeg
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/cache-apt-packages
|
||||
with:
|
||||
packages: libturbojpeg
|
||||
version: ${{ env.APT_CACHE_VERSION }}
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ needs.info.outputs.default_python }}
|
||||
python-version-file: ".python-version"
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -516,10 +569,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ needs.info.outputs.default_python }}
|
||||
python-version-file: ".python-version"
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -552,10 +605,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ needs.info.outputs.default_python }}
|
||||
python-version-file: ".python-version"
|
||||
check-latest: true
|
||||
- name: Run gen_copilot_instructions.py
|
||||
run: |
|
||||
python -m script.gen_copilot_instructions validate
|
||||
@@ -607,10 +660,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -658,10 +711,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ needs.info.outputs.default_python }}
|
||||
python-version-file: ".python-version"
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -711,10 +764,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ needs.info.outputs.default_python }}
|
||||
python-version-file: ".python-version"
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -762,10 +815,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ needs.info.outputs.default_python }}
|
||||
python-version-file: ".python-version"
|
||||
check-latest: true
|
||||
- name: Generate partial mypy restore key
|
||||
id: generate-mypy-key
|
||||
run: |
|
||||
@@ -823,26 +876,38 @@ jobs:
|
||||
- info
|
||||
- base
|
||||
steps:
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.APT_CACHE_DIR }}
|
||||
${{ env.APT_LIST_CACHE_DIR }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get update \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR}
|
||||
sudo apt-get -y install \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR} \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/cache-apt-packages
|
||||
with:
|
||||
packages: >-
|
||||
bluez
|
||||
ffmpeg
|
||||
libturbojpeg
|
||||
version: ${{ env.APT_CACHE_VERSION }}
|
||||
execute_install_scripts: true
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ needs.info.outputs.default_python }}
|
||||
python-version-file: ".python-version"
|
||||
check-latest: true
|
||||
- name: Restore full Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -887,27 +952,39 @@ jobs:
|
||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
||||
group: ${{ fromJson(needs.info.outputs.test_groups) }}
|
||||
steps:
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.APT_CACHE_DIR }}
|
||||
${{ env.APT_LIST_CACHE_DIR }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get update \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR}
|
||||
sudo apt-get -y install \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR} \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libxml2-utils
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/cache-apt-packages
|
||||
with:
|
||||
packages: >-
|
||||
bluez
|
||||
ffmpeg
|
||||
libturbojpeg
|
||||
libxml2-utils
|
||||
version: ${{ env.APT_CACHE_VERSION }}
|
||||
execute_install_scripts: true
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -1028,28 +1105,40 @@ jobs:
|
||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
||||
mariadb-group: ${{ fromJson(needs.info.outputs.mariadb_groups) }}
|
||||
steps:
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.APT_CACHE_DIR }}
|
||||
${{ env.APT_LIST_CACHE_DIR }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get update \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR}
|
||||
sudo apt-get -y install \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR} \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libmariadb-dev-compat \
|
||||
libxml2-utils
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/cache-apt-packages
|
||||
with:
|
||||
packages: >-
|
||||
bluez
|
||||
ffmpeg
|
||||
libturbojpeg
|
||||
libmariadb-dev-compat
|
||||
libxml2-utils
|
||||
version: ${{ env.APT_CACHE_VERSION }}
|
||||
execute_install_scripts: true
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -1177,35 +1266,42 @@ jobs:
|
||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
||||
postgresql-group: ${{ fromJson(needs.info.outputs.postgresql_groups) }}
|
||||
steps:
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.APT_CACHE_DIR }}
|
||||
${{ env.APT_LIST_CACHE_DIR }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get update \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR}
|
||||
sudo apt-get -y install \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR} \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libxml2-utils
|
||||
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
|
||||
sudo apt-get -y install \
|
||||
postgresql-server-dev-14
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/cache-apt-packages
|
||||
with:
|
||||
packages: >-
|
||||
bluez
|
||||
ffmpeg
|
||||
libturbojpeg
|
||||
libxml2-utils
|
||||
version: ${{ env.APT_CACHE_VERSION }}
|
||||
execute_install_scripts: true
|
||||
- name: Set up PostgreSQL apt repository
|
||||
run: sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
|
||||
- name: Cache PostgreSQL development headers
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/cache-apt-packages
|
||||
with:
|
||||
packages: postgresql-server-dev-14
|
||||
version: ${{ env.APT_CACHE_VERSION }}
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -1353,27 +1449,39 @@ jobs:
|
||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
||||
group: ${{ fromJson(needs.info.outputs.test_groups) }}
|
||||
steps:
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
${{ env.APT_CACHE_DIR }}
|
||||
${{ env.APT_LIST_CACHE_DIR }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get update \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR}
|
||||
sudo apt-get -y install \
|
||||
-o Dir::Cache=${APT_CACHE_DIR} \
|
||||
-o Dir::State::Lists=${APT_LIST_CACHE_DIR} \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libxml2-utils
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install additional OS dependencies
|
||||
timeout-minutes: 10
|
||||
uses: ./.github/actions/cache-apt-packages
|
||||
with:
|
||||
packages: >-
|
||||
bluez
|
||||
ffmpeg
|
||||
libturbojpeg
|
||||
libxml2-utils
|
||||
version: ${{ env.APT_CACHE_VERSION }}
|
||||
execute_install_scripts: true
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: ./.github/actions/setup-uv-python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
uv-version: ${{ needs.info.outputs.uv_version }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
|
||||
@@ -134,7 +134,7 @@ class AuthManagerFlowManager(
|
||||
"""
|
||||
flow = cast(LoginFlow, flow)
|
||||
|
||||
if result["type"] is not FlowResultType.CREATE_ENTRY:
|
||||
if result["type"] != FlowResultType.CREATE_ENTRY:
|
||||
return result
|
||||
|
||||
# we got final result
|
||||
|
||||
@@ -226,7 +226,7 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
) -> SubentryFlowResult:
|
||||
"""Set initial options."""
|
||||
# abort if entry is not loaded
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
hass_apis: list[SelectOptionDict] = [
|
||||
|
||||
@@ -89,7 +89,7 @@ class AOSmithWaterHeaterEntity(AOSmithStatusEntity, WaterHeaterEntity):
|
||||
def supported_features(self) -> WaterHeaterEntityFeature:
|
||||
"""Return the list of supported features."""
|
||||
supports_vacation_mode = any(
|
||||
supported_mode.mode is AOSmithOperationMode.VACATION
|
||||
supported_mode.mode == AOSmithOperationMode.VACATION
|
||||
for supported_mode in self.device.supported_modes
|
||||
)
|
||||
|
||||
@@ -122,7 +122,7 @@ class AOSmithWaterHeaterEntity(AOSmithStatusEntity, WaterHeaterEntity):
|
||||
@property
|
||||
def is_away_mode_on(self) -> bool:
|
||||
"""Return True if away mode is on."""
|
||||
return self.device.status.current_mode is AOSmithOperationMode.VACATION
|
||||
return self.device.status.current_mode == AOSmithOperationMode.VACATION
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
||||
"""Set new target operation mode."""
|
||||
|
||||
@@ -369,7 +369,7 @@ class AppleTVManager(DeviceListener):
|
||||
|
||||
attrs[ATTR_MODEL] = (
|
||||
dev_info.raw_model
|
||||
if dev_info.model is DeviceModel.Unknown and dev_info.raw_model
|
||||
if dev_info.model == DeviceModel.Unknown and dev_info.raw_model
|
||||
else model_str(dev_info.model)
|
||||
)
|
||||
attrs[ATTR_SW_VERSION] = dev_info.version
|
||||
|
||||
@@ -63,7 +63,7 @@ class AppleTVKeyboardFocused(AppleTVEntity, BinarySensorEntity, KeyboardListener
|
||||
# Listen to keyboard updates
|
||||
atv.keyboard.listener = self
|
||||
# Set initial state based on current focus state
|
||||
self._update_state(atv.keyboard.text_focus_state is KeyboardFocusState.Focused)
|
||||
self._update_state(atv.keyboard.text_focus_state == KeyboardFocusState.Focused)
|
||||
|
||||
@callback
|
||||
def async_device_disconnected(self) -> None:
|
||||
@@ -78,7 +78,7 @@ class AppleTVKeyboardFocused(AppleTVEntity, BinarySensorEntity, KeyboardListener
|
||||
|
||||
This is a callback function from pyatv.interface.KeyboardListener.
|
||||
"""
|
||||
self._update_state(new_state is KeyboardFocusState.Focused)
|
||||
self._update_state(new_state == KeyboardFocusState.Focused)
|
||||
|
||||
def _update_state(self, new_state: bool) -> None:
|
||||
"""Update and report."""
|
||||
|
||||
@@ -354,7 +354,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"name": self.atv.name,
|
||||
"type": (
|
||||
dev_info.raw_model
|
||||
if dev_info.model is DeviceModel.Unknown and dev_info.raw_model
|
||||
if dev_info.model == DeviceModel.Unknown and dev_info.raw_model
|
||||
else model_str(dev_info.model)
|
||||
),
|
||||
}
|
||||
@@ -441,12 +441,12 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return await self.async_step_password()
|
||||
|
||||
# Figure out, depending on protocol, what kind of pairing is needed
|
||||
if service.pairing is PairingRequirement.Unsupported:
|
||||
if service.pairing == PairingRequirement.Unsupported:
|
||||
_LOGGER.debug("%s does not support pairing", self.protocol)
|
||||
return await self.async_pair_next_protocol()
|
||||
if service.pairing is PairingRequirement.Disabled:
|
||||
if service.pairing == PairingRequirement.Disabled:
|
||||
return await self.async_step_protocol_disabled()
|
||||
if service.pairing is PairingRequirement.NotNeeded:
|
||||
if service.pairing == PairingRequirement.NotNeeded:
|
||||
_LOGGER.debug("%s does not require pairing", self.protocol)
|
||||
self.credentials[self.protocol.value] = None
|
||||
return await self.async_pair_next_protocol()
|
||||
@@ -457,7 +457,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
pair_args: dict[str, Any] = {}
|
||||
if self.protocol in {Protocol.AirPlay, Protocol.Companion, Protocol.DMAP}:
|
||||
pair_args["name"] = "Home Assistant"
|
||||
if self.protocol is Protocol.DMAP:
|
||||
if self.protocol == Protocol.DMAP:
|
||||
pair_args["zeroconf"] = await zeroconf.async_get_instance(self.hass)
|
||||
|
||||
# Initiate the pairing process
|
||||
|
||||
@@ -139,7 +139,7 @@ class AppleTvMediaPlayer(
|
||||
all_features = atv.features.all_features()
|
||||
for feature_name, support_flag in SUPPORT_FEATURE_MAPPING.items():
|
||||
feature_info = all_features.get(feature_name)
|
||||
if feature_info and feature_info.state is not FeatureState.Unsupported:
|
||||
if feature_info and feature_info.state != FeatureState.Unsupported:
|
||||
self._attr_supported_features |= support_flag
|
||||
|
||||
# No need to schedule state update here as that will happen when the first
|
||||
@@ -188,14 +188,14 @@ class AppleTvMediaPlayer(
|
||||
return MediaPlayerState.OFF
|
||||
if (
|
||||
self._is_feature_available(FeatureName.PowerState)
|
||||
and self.atv.power.power_state is PowerState.Off
|
||||
and self.atv.power.power_state == PowerState.Off
|
||||
):
|
||||
return MediaPlayerState.OFF
|
||||
if self._playing:
|
||||
state = self._playing.device_state
|
||||
if state in (DeviceState.Idle, DeviceState.Loading):
|
||||
return MediaPlayerState.IDLE
|
||||
if state is DeviceState.Playing:
|
||||
if state == DeviceState.Playing:
|
||||
return MediaPlayerState.PLAYING
|
||||
if state in (DeviceState.Paused, DeviceState.Seeking, DeviceState.Stopped):
|
||||
return MediaPlayerState.PAUSED
|
||||
@@ -446,7 +446,7 @@ class AppleTvMediaPlayer(
|
||||
def shuffle(self) -> bool | None:
|
||||
"""Boolean if shuffle is enabled."""
|
||||
if self._playing and self._is_feature_available(FeatureName.Shuffle):
|
||||
return self._playing.shuffle is not ShuffleState.Off
|
||||
return self._playing.shuffle != ShuffleState.Off
|
||||
return None
|
||||
|
||||
def _is_feature_available(self, feature: FeatureName) -> bool:
|
||||
@@ -506,7 +506,7 @@ class AppleTvMediaPlayer(
|
||||
and (self._is_feature_available(FeatureName.TurnOff))
|
||||
and (
|
||||
not self._is_feature_available(FeatureName.PowerState)
|
||||
or self.atv.power.power_state is PowerState.On
|
||||
or self.atv.power.power_state == PowerState.On
|
||||
)
|
||||
):
|
||||
await self.atv.power.turn_off()
|
||||
|
||||
@@ -59,7 +59,7 @@ def _check_keyboard_focus(atv: AppleTVInterface) -> None:
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="keyboard_not_available",
|
||||
) from err
|
||||
if focus_state is not KeyboardFocusState.Focused:
|
||||
if focus_state != KeyboardFocusState.Focused:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="keyboard_not_focused",
|
||||
|
||||
@@ -263,9 +263,9 @@ class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
|
||||
def media_channel(self) -> str | None:
|
||||
"""Channel currently playing."""
|
||||
source = self._state.get_source()
|
||||
if source is SourceCodes.DAB:
|
||||
if source == SourceCodes.DAB:
|
||||
value = self._state.get_dab_station()
|
||||
elif source is SourceCodes.FM:
|
||||
elif source == SourceCodes.FM:
|
||||
value = self._state.get_rds_information()
|
||||
else:
|
||||
value = None
|
||||
@@ -274,7 +274,7 @@ class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
|
||||
@property
|
||||
def media_artist(self) -> str | None:
|
||||
"""Artist of current playing media, music track only."""
|
||||
if self._state.get_source() is SourceCodes.DAB:
|
||||
if self._state.get_source() == SourceCodes.DAB:
|
||||
value = self._state.get_dls_pdt()
|
||||
else:
|
||||
value = None
|
||||
|
||||
@@ -1355,7 +1355,7 @@ class PipelineRun:
|
||||
) -> bool:
|
||||
"""Return true if all targeted entities were in the same area as the device."""
|
||||
if (
|
||||
intent_response.response_type is not intent.IntentResponseType.ACTION_DONE
|
||||
intent_response.response_type != intent.IntentResponseType.ACTION_DONE
|
||||
or not intent_response.matched_states
|
||||
):
|
||||
return False
|
||||
|
||||
@@ -251,12 +251,12 @@ class AuthProvidersView(HomeAssistantView):
|
||||
|
||||
def _prepare_result_json(result: AuthFlowResult) -> dict[str, Any]:
|
||||
"""Convert result to JSON serializable dict."""
|
||||
if result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
return {
|
||||
key: val for key, val in result.items() if key not in ("result", "data")
|
||||
}
|
||||
|
||||
if result["type"] is not data_entry_flow.FlowResultType.FORM:
|
||||
if result["type"] != data_entry_flow.FlowResultType.FORM:
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
data = dict(result)
|
||||
@@ -289,11 +289,11 @@ class LoginFlowBaseView(HomeAssistantView):
|
||||
result: AuthFlowResult,
|
||||
) -> web.Response:
|
||||
"""Convert the flow result to a response."""
|
||||
if result["type"] is not data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
# @log_invalid_auth does not work here since it returns HTTP 200.
|
||||
# We need to manually log failed login attempts.
|
||||
if (
|
||||
result["type"] is data_entry_flow.FlowResultType.FORM
|
||||
result["type"] == data_entry_flow.FlowResultType.FORM
|
||||
and (errors := result.get("errors"))
|
||||
and errors.get("base")
|
||||
in (
|
||||
|
||||
@@ -142,9 +142,9 @@ def websocket_depose_mfa(
|
||||
|
||||
def _prepare_result_json(result: data_entry_flow.FlowResult) -> dict[str, Any]:
|
||||
"""Convert result to JSON serializable dict."""
|
||||
if result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
return dict(result)
|
||||
if result["type"] is not data_entry_flow.FlowResultType.FORM:
|
||||
if result["type"] != data_entry_flow.FlowResultType.FORM:
|
||||
return result # type: ignore[return-value]
|
||||
|
||||
data = dict(result)
|
||||
|
||||
@@ -273,7 +273,7 @@ async def ws_subscribe_scanner_details(
|
||||
|
||||
def _async_registration_changed(registration: HaScannerRegistration) -> None:
|
||||
added_event = HaScannerRegistrationEvent.ADDED
|
||||
event_type = "add" if registration.event is added_event else "remove"
|
||||
event_type = "add" if registration.event == added_event else "remove"
|
||||
_async_event_message({event_type: [registration.scanner.details]})
|
||||
|
||||
manager = _get_manager(hass)
|
||||
|
||||
@@ -158,10 +158,7 @@ def process_service_info(
|
||||
)
|
||||
|
||||
# If payload is encrypted and the bindkey is not verified then we need to reauth
|
||||
if (
|
||||
data.encryption_scheme is not EncryptionScheme.NONE
|
||||
and not data.bindkey_verified
|
||||
):
|
||||
if data.encryption_scheme != EncryptionScheme.NONE and not data.bindkey_verified:
|
||||
entry.async_start_reauth(hass, data={"device": data})
|
||||
|
||||
return update
|
||||
|
||||
@@ -59,7 +59,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._discovery_info = discovery_info
|
||||
self._discovered_device = device
|
||||
|
||||
if device.encryption_scheme is EncryptionScheme.BTHOME_BINDKEY:
|
||||
if device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
|
||||
return await self.async_step_get_encryption_key()
|
||||
return await self.async_step_bluetooth_confirm()
|
||||
|
||||
@@ -125,7 +125,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._discovery_info = discovery.discovery_info
|
||||
self._discovered_device = discovery.device
|
||||
|
||||
if discovery.device.encryption_scheme is EncryptionScheme.BTHOME_BINDKEY:
|
||||
if discovery.device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
|
||||
return await self.async_step_get_encryption_key()
|
||||
|
||||
return self._async_get_or_create_entry()
|
||||
@@ -164,7 +164,7 @@ class BTHomeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self._discovery_info = device.last_service_info
|
||||
|
||||
if device.encryption_scheme is EncryptionScheme.BTHOME_BINDKEY:
|
||||
if device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
|
||||
return await self.async_step_get_encryption_key()
|
||||
|
||||
# Otherwise there wasn't actually encryption so abort
|
||||
|
||||
@@ -110,7 +110,7 @@ class ComelitAlarmEntity(
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if alarm is available."""
|
||||
if self._area.human_status is AlarmAreaState.UNKNOWN:
|
||||
if self._area.human_status == AlarmAreaState.UNKNOWN:
|
||||
return False
|
||||
return super().available
|
||||
|
||||
@@ -124,7 +124,7 @@ class ComelitAlarmEntity(
|
||||
self._area.human_status,
|
||||
self._area.armed,
|
||||
)
|
||||
if self._area.human_status is AlarmAreaState.ARMED:
|
||||
if self._area.human_status == AlarmAreaState.ARMED:
|
||||
if self._area.armed == ALARM_AREA_ARMED_STATUS[AWAY]:
|
||||
return AlarmControlPanelState.ARMED_AWAY
|
||||
if self._area.armed == ALARM_AREA_ARMED_STATUS[NIGHT]:
|
||||
|
||||
@@ -43,7 +43,7 @@ BINARY_SENSOR_TYPES: Final[tuple[ComelitBinarySensorEntityDescription, ...]] = (
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
is_on_fn=lambda obj: cast(ComelitVedoAreaObject, obj).anomaly,
|
||||
available_fn=lambda obj: (
|
||||
cast(ComelitVedoAreaObject, obj).human_status is not AlarmAreaState.UNKNOWN
|
||||
cast(ComelitVedoAreaObject, obj).human_status != AlarmAreaState.UNKNOWN
|
||||
),
|
||||
),
|
||||
ComelitBinarySensorEntityDescription(
|
||||
@@ -67,7 +67,7 @@ BINARY_SENSOR_TYPES: Final[tuple[ComelitBinarySensorEntityDescription, ...]] = (
|
||||
object_type=ALARM_ZONE,
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
is_on_fn=lambda obj: (
|
||||
cast(ComelitVedoZoneObject, obj).human_status is AlarmZoneState.FAULTY
|
||||
cast(ComelitVedoZoneObject, obj).human_status == AlarmZoneState.FAULTY
|
||||
),
|
||||
available_fn=lambda obj: (
|
||||
cast(ComelitVedoZoneObject, obj).human_status
|
||||
|
||||
@@ -166,12 +166,12 @@ class ComelitVedoSensorEntity(
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Sensor availability."""
|
||||
return self._zone_object.human_status is not AlarmZoneState.UNAVAILABLE
|
||||
return self._zone_object.human_status != AlarmZoneState.UNAVAILABLE
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Sensor value."""
|
||||
if (status := self._zone_object.human_status) is AlarmZoneState.UNKNOWN:
|
||||
if (status := self._zone_object.human_status) == AlarmZoneState.UNKNOWN:
|
||||
return None
|
||||
|
||||
return cast(str, status.value)
|
||||
|
||||
@@ -148,7 +148,7 @@ def _prepare_config_flow_result_json(
|
||||
prepare_result_json: Callable[[data_entry_flow.FlowResult], dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
"""Convert result to JSON."""
|
||||
if result["type"] is not data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
return prepare_result_json(result)
|
||||
|
||||
data = {key: val for key, val in result.items() if key not in ("data", "context")}
|
||||
|
||||
@@ -646,7 +646,7 @@ class DefaultAgent(ConversationEntity):
|
||||
cache_value = self._intent_cache.get(cache_key)
|
||||
if cache_value is not None:
|
||||
if (cache_value.result is not None) and (
|
||||
cache_value.stage is IntentMatchingStage.EXPOSED_ENTITIES_ONLY
|
||||
cache_value.stage == IntentMatchingStage.EXPOSED_ENTITIES_ONLY
|
||||
):
|
||||
_LOGGER.debug("Got cached result for exposed entities")
|
||||
return cache_value.result
|
||||
@@ -686,7 +686,7 @@ class DefaultAgent(ConversationEntity):
|
||||
skip_unexposed_entities_match = False
|
||||
if cache_value is not None:
|
||||
if (cache_value.result is not None) and (
|
||||
cache_value.stage is IntentMatchingStage.UNEXPOSED_ENTITIES
|
||||
cache_value.stage == IntentMatchingStage.UNEXPOSED_ENTITIES
|
||||
):
|
||||
_LOGGER.debug("Got cached result for all entities")
|
||||
return cache_value.result
|
||||
@@ -731,7 +731,7 @@ class DefaultAgent(ConversationEntity):
|
||||
skip_unknown_names = False
|
||||
if cache_value is not None:
|
||||
if (cache_value.result is not None) and (
|
||||
cache_value.stage is IntentMatchingStage.UNKNOWN_NAMES
|
||||
cache_value.stage == IntentMatchingStage.UNKNOWN_NAMES
|
||||
):
|
||||
_LOGGER.debug("Got cached result for unknown names")
|
||||
return cache_value.result
|
||||
@@ -1546,7 +1546,7 @@ def _get_match_error_response(
|
||||
# device_class only
|
||||
return ErrorKey.NO_DEVICE_CLASS, {"device_class": device_class}
|
||||
|
||||
if (reason is intent.MatchFailedReason.DOMAIN) and constraints.domains:
|
||||
if (reason == intent.MatchFailedReason.DOMAIN) and constraints.domains:
|
||||
domain = next(iter(constraints.domains)) # first domain
|
||||
if constraints.area_name:
|
||||
# domain in area
|
||||
@@ -1565,7 +1565,7 @@ def _get_match_error_response(
|
||||
# domain only
|
||||
return ErrorKey.NO_DOMAIN, {"domain": domain}
|
||||
|
||||
if reason is intent.MatchFailedReason.DUPLICATE_NAME:
|
||||
if reason == intent.MatchFailedReason.DUPLICATE_NAME:
|
||||
if constraints.floor_name:
|
||||
# duplicate on floor
|
||||
return ErrorKey.DUPLICATE_ENTITIES_IN_FLOOR, {
|
||||
@@ -1582,26 +1582,26 @@ def _get_match_error_response(
|
||||
|
||||
return ErrorKey.DUPLICATE_ENTITIES, {"entity": result.no_match_name}
|
||||
|
||||
if reason is intent.MatchFailedReason.INVALID_AREA:
|
||||
if reason == intent.MatchFailedReason.INVALID_AREA:
|
||||
# Invalid area name
|
||||
return ErrorKey.NO_AREA, {"area": result.no_match_name}
|
||||
|
||||
if reason is intent.MatchFailedReason.INVALID_FLOOR:
|
||||
if reason == intent.MatchFailedReason.INVALID_FLOOR:
|
||||
# Invalid floor name
|
||||
return ErrorKey.NO_FLOOR, {"floor": result.no_match_name}
|
||||
|
||||
if reason is intent.MatchFailedReason.FEATURE:
|
||||
if reason == intent.MatchFailedReason.FEATURE:
|
||||
# Feature not supported by entity
|
||||
return ErrorKey.FEATURE_NOT_SUPPORTED, {}
|
||||
|
||||
if reason is intent.MatchFailedReason.STATE:
|
||||
if reason == intent.MatchFailedReason.STATE:
|
||||
# Entity is not in correct state
|
||||
assert constraints.states
|
||||
state = next(iter(constraints.states))
|
||||
|
||||
return ErrorKey.ENTITY_WRONG_STATE, {"state": state}
|
||||
|
||||
if reason is intent.MatchFailedReason.ASSISTANT:
|
||||
if reason == intent.MatchFailedReason.ASSISTANT:
|
||||
# Not exposed
|
||||
if constraints.name:
|
||||
if constraints.area_name:
|
||||
|
||||
@@ -80,7 +80,7 @@ async def async_validate_device_automation_config(
|
||||
# the checks below which look for a config entry matching the device automation
|
||||
# domain
|
||||
if (
|
||||
automation_type is DeviceAutomationType.ACTION
|
||||
automation_type == DeviceAutomationType.ACTION
|
||||
and validated_config[CONF_DOMAIN] in ENTITY_PLATFORMS
|
||||
):
|
||||
# Pass the unvalidated config to avoid mutating the raw config twice
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Code to set up a device tracker platform using a config entry."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any, final
|
||||
|
||||
from propcache.api import cached_property
|
||||
@@ -16,7 +17,13 @@ from homeassistant.const import (
|
||||
STATE_NOT_HOME,
|
||||
EntityCategory,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, State, callback
|
||||
from homeassistant.core import (
|
||||
Event,
|
||||
HomeAssistant,
|
||||
State,
|
||||
async_get_hass_or_none,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.device_registry import (
|
||||
DeviceInfo,
|
||||
@@ -27,6 +34,7 @@ from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.loader import async_suggest_report_issue
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import (
|
||||
@@ -41,6 +49,8 @@ from .const import (
|
||||
SourceType,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_COMPONENT: HassKey[EntityComponent[BaseTrackerEntity]] = HassKey(DOMAIN)
|
||||
DATA_KEY: HassKey[dict[str, tuple[str, str]]] = HassKey(f"{DOMAIN}_mac")
|
||||
|
||||
@@ -176,11 +186,31 @@ class BaseTrackerEntity(Entity):
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_source_type: SourceType
|
||||
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
"""Post initialisation processing."""
|
||||
super().__init_subclass__(**kwargs)
|
||||
if "battery_level" in cls.__dict__:
|
||||
report_issue = async_suggest_report_issue(
|
||||
async_get_hass_or_none(), module=cls.__module__
|
||||
)
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"%s::%s is overriding the deprecated battery_level method on "
|
||||
"an instance of TrackerEntity, this will be unsupported from "
|
||||
"Home Assistant 2027.6, please %s"
|
||||
),
|
||||
cls.__module__,
|
||||
cls.__name__,
|
||||
report_issue,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def battery_level(self) -> int | None:
|
||||
"""Return the battery level of the device.
|
||||
|
||||
Percentage from 0-100.
|
||||
|
||||
The property is deprecated and will be removed in Home Assistant 2027.6.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
||||
except KeyError, ValueError:
|
||||
bootid = None
|
||||
|
||||
if change is ssdp.SsdpChange.UPDATE:
|
||||
if change == ssdp.SsdpChange.UPDATE:
|
||||
# This is an announcement that bootid is about to change
|
||||
if self._bootid is not None and self._bootid == bootid:
|
||||
# Store the new value (because our old value matches) so that we
|
||||
@@ -281,7 +281,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
||||
await self._device_disconnect()
|
||||
self._bootid = bootid
|
||||
|
||||
if change is ssdp.SsdpChange.BYEBYE:
|
||||
if change == ssdp.SsdpChange.BYEBYE:
|
||||
# Device is going away
|
||||
if self._device:
|
||||
# Disconnect from gone device
|
||||
@@ -290,7 +290,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
||||
self._ssdp_connect_failed = False
|
||||
|
||||
if (
|
||||
change is ssdp.SsdpChange.ALIVE
|
||||
change == ssdp.SsdpChange.ALIVE
|
||||
and not self._device
|
||||
and not self._ssdp_connect_failed
|
||||
):
|
||||
@@ -718,7 +718,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
||||
|
||||
# If already playing, or don't want to autoplay, no need to call Play
|
||||
autoplay = extra.get("autoplay", True)
|
||||
if self._device.transport_state is TransportState.PLAYING or not autoplay:
|
||||
if self._device.transport_state == TransportState.PLAYING or not autoplay:
|
||||
return
|
||||
|
||||
# Play it
|
||||
@@ -748,7 +748,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
||||
if not (play_mode := self._device.play_mode):
|
||||
return None
|
||||
|
||||
if play_mode is PlayMode.VENDOR_DEFINED:
|
||||
if play_mode == PlayMode.VENDOR_DEFINED:
|
||||
return None
|
||||
|
||||
return play_mode in (PlayMode.SHUFFLE, PlayMode.RANDOM)
|
||||
@@ -782,10 +782,10 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
||||
if not (play_mode := self._device.play_mode):
|
||||
return None
|
||||
|
||||
if play_mode is PlayMode.VENDOR_DEFINED:
|
||||
if play_mode == PlayMode.VENDOR_DEFINED:
|
||||
return None
|
||||
|
||||
if play_mode is PlayMode.REPEAT_ONE:
|
||||
if play_mode == PlayMode.REPEAT_ONE:
|
||||
return RepeatMode.ONE
|
||||
|
||||
if play_mode in (PlayMode.REPEAT_ALL, PlayMode.RANDOM):
|
||||
|
||||
@@ -236,7 +236,7 @@ class DmsDeviceSource:
|
||||
except KeyError, ValueError:
|
||||
bootid = None
|
||||
|
||||
if change is ssdp.SsdpChange.UPDATE:
|
||||
if change == ssdp.SsdpChange.UPDATE:
|
||||
# This is an announcement that bootid is about to change
|
||||
if self._bootid is not None and self._bootid == bootid:
|
||||
# Store the new value (because our old value matches) so that we
|
||||
@@ -258,7 +258,7 @@ class DmsDeviceSource:
|
||||
await self.device_disconnect()
|
||||
self._bootid = bootid
|
||||
|
||||
if change is ssdp.SsdpChange.BYEBYE:
|
||||
if change == ssdp.SsdpChange.BYEBYE:
|
||||
# Device is going away
|
||||
if self._device:
|
||||
# Disconnect from gone device
|
||||
@@ -267,7 +267,7 @@ class DmsDeviceSource:
|
||||
self._ssdp_connect_failed = False
|
||||
|
||||
if (
|
||||
change is ssdp.SsdpChange.ALIVE
|
||||
change == ssdp.SsdpChange.ALIVE
|
||||
and not self._device
|
||||
and not self._ssdp_connect_failed
|
||||
):
|
||||
|
||||
@@ -8,12 +8,6 @@
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"download_dir": "Download directory"
|
||||
},
|
||||
"data_description": {
|
||||
"download_dir": "The directory where downloaded files will be stored. This can be an absolute path or a path relative to the Home Assistant configuration directory."
|
||||
},
|
||||
"description": "Select a location to get to store downloads. The setup will check if the directory exists."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,9 +150,9 @@ class ElevenLabsSTTEntity(SpeechToTextEntity):
|
||||
|
||||
raw_pcm_compatible = (
|
||||
metadata.codec == AudioCodecs.PCM
|
||||
and metadata.sample_rate is AudioSampleRates.SAMPLERATE_16000
|
||||
and metadata.channel is AudioChannels.CHANNEL_MONO
|
||||
and metadata.bit_rate is AudioBitRates.BITRATE_16
|
||||
and metadata.sample_rate == AudioSampleRates.SAMPLERATE_16000
|
||||
and metadata.channel == AudioChannels.CHANNEL_MONO
|
||||
and metadata.bit_rate == AudioBitRates.BITRATE_16
|
||||
)
|
||||
if raw_pcm_compatible:
|
||||
file_format = "pcm_s16le_16"
|
||||
|
||||
@@ -50,5 +50,5 @@ class ElkBinarySensor(ElkAttachedEntity, BinarySensorEntity):
|
||||
def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
|
||||
# Zone in NORMAL state is OFF; any other state is ON
|
||||
self._attr_is_on = bool(
|
||||
self._element.logical_status is not ZoneLogicalStatus.NORMAL
|
||||
self._element.logical_status != ZoneLogicalStatus.NORMAL
|
||||
)
|
||||
|
||||
@@ -104,7 +104,7 @@ class ElkThermostat(ElkEntity, ClimateEntity):
|
||||
ThermostatMode.EMERGENCY_HEAT,
|
||||
):
|
||||
return self._element.heat_setpoint
|
||||
if self._element.mode is ThermostatMode.COOL:
|
||||
if self._element.mode == ThermostatMode.COOL:
|
||||
return self._element.cool_setpoint
|
||||
return None
|
||||
|
||||
@@ -162,6 +162,6 @@ class ElkThermostat(ElkEntity, ClimateEntity):
|
||||
self._attr_hvac_mode = ELK_TO_HASS_HVAC_MODES[self._element.mode]
|
||||
if (
|
||||
self._attr_hvac_mode == HVACMode.OFF
|
||||
and self._element.fan is ThermostatFan.ON
|
||||
and self._element.fan == ThermostatFan.ON
|
||||
):
|
||||
self._attr_hvac_mode = HVACMode.FAN_ONLY
|
||||
|
||||
@@ -56,7 +56,7 @@ class ElkNumberSetting(ElkAttachedEntity, NumberEntity):
|
||||
def __init__(self, element: Setting, elk: Any, elk_data: ELKM1Data) -> None:
|
||||
"""Initialize the number setting."""
|
||||
super().__init__(element, elk, elk_data)
|
||||
if element.value_format is SettingFormat.TIMER:
|
||||
if element.value_format == SettingFormat.TIMER:
|
||||
self._attr_device_class = NumberDeviceClass.DURATION
|
||||
self._attr_native_unit_of_measurement = UnitOfTime.SECONDS
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ async def async_setup_entry(
|
||||
for setting in elk.settings:
|
||||
setting = cast(Setting, setting)
|
||||
domain = (
|
||||
"time" if setting.value_format is SettingFormat.TIME_OF_DAY else "number"
|
||||
"time" if setting.value_format == SettingFormat.TIME_OF_DAY else "number"
|
||||
)
|
||||
|
||||
orig_unique_id = generate_unique_id(elk_data.prefix, setting)
|
||||
@@ -288,7 +288,7 @@ class ElkZone(ElkSensor):
|
||||
@property
|
||||
def temperature_unit(self) -> str | None:
|
||||
"""Return the temperature unit."""
|
||||
if self._element.definition is ZoneType.TEMPERATURE:
|
||||
if self._element.definition == ZoneType.TEMPERATURE:
|
||||
return self._temperature_unit
|
||||
return None
|
||||
|
||||
@@ -305,18 +305,18 @@ class ElkZone(ElkSensor):
|
||||
@property
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit of measurement."""
|
||||
if self._element.definition is ZoneType.TEMPERATURE:
|
||||
if self._element.definition == ZoneType.TEMPERATURE:
|
||||
return self._temperature_unit
|
||||
if self._element.definition is ZoneType.ANALOG_ZONE:
|
||||
if self._element.definition == ZoneType.ANALOG_ZONE:
|
||||
return UnitOfElectricPotential.VOLT
|
||||
return None
|
||||
|
||||
def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
|
||||
if self._element.definition is ZoneType.TEMPERATURE:
|
||||
if self._element.definition == ZoneType.TEMPERATURE:
|
||||
self._attr_native_value = temperature_to_state(
|
||||
self._element.temperature, UNDEFINED_TEMPERATURE
|
||||
)
|
||||
elif self._element.definition is ZoneType.ANALOG_ZONE:
|
||||
elif self._element.definition == ZoneType.ANALOG_ZONE:
|
||||
self._attr_native_value = f"{self._element.voltage}"
|
||||
else:
|
||||
self._attr_native_value = pretty_const(self._element.logical_status.name)
|
||||
|
||||
@@ -66,7 +66,7 @@ class ElkThermostatEMHeat(ElkEntity, SwitchEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Get the current emergency heat status."""
|
||||
return self._element.mode is ThermostatMode.EMERGENCY_HEAT
|
||||
return self._element.mode == ThermostatMode.EMERGENCY_HEAT
|
||||
|
||||
def _elk_set(self, mode: ThermostatMode) -> None:
|
||||
"""Set the thermostat mode."""
|
||||
|
||||
@@ -30,7 +30,7 @@ async def async_setup_entry(
|
||||
time_settings = [
|
||||
setting
|
||||
for setting in cast(list[Setting], elk.settings)
|
||||
if setting.value_format is SettingFormat.TIME_OF_DAY
|
||||
if setting.value_format == SettingFormat.TIME_OF_DAY
|
||||
]
|
||||
|
||||
create_elk_entities(
|
||||
|
||||
@@ -96,7 +96,7 @@ def __get_coordinator(call: ServiceCall) -> EnergyZeroDataUpdateCoordinator:
|
||||
"config_entry": entry_id,
|
||||
},
|
||||
)
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unloaded_config_entry",
|
||||
@@ -125,7 +125,7 @@ async def __get_prices(
|
||||
|
||||
data: Electricity | Gas
|
||||
|
||||
if price_type is PriceType.GAS:
|
||||
if price_type == PriceType.GAS:
|
||||
data = await coordinator.energyzero.get_gas_prices_legacy(
|
||||
start_date=start,
|
||||
end_date=end,
|
||||
|
||||
@@ -24,7 +24,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: FastdotcomConfigEntry) -
|
||||
|
||||
async def _async_finish_startup(hass: HomeAssistant) -> None:
|
||||
"""Run this only when HA has finished its startup."""
|
||||
if entry.state is ConfigEntryState.LOADED:
|
||||
if entry.state == ConfigEntryState.LOADED:
|
||||
await coordinator.async_refresh()
|
||||
else:
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
@@ -284,7 +284,7 @@ class FishAudioSubentryFlowHandler(ConfigSubentryFlow):
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage initial options."""
|
||||
entry = self._get_entry()
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
self.client = entry.runtime_data
|
||||
|
||||
@@ -109,7 +109,7 @@ def setup_service(hass: HomeAssistant) -> None:
|
||||
entry: FlumeConfigEntry | None = hass.config_entries.async_get_entry(entry_id)
|
||||
if not entry:
|
||||
raise ValueError(f"Invalid config entry: {entry_id}")
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if not entry.state == ConfigEntryState.LOADED:
|
||||
raise ValueError(f"Config entry not loaded: {entry_id}")
|
||||
return {
|
||||
"notifications": entry.runtime_data.notifications_coordinator.notifications # type: ignore[dict-item]
|
||||
|
||||
@@ -136,7 +136,7 @@ class FluxLedConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
ConfigEntryState.SETUP_IN_PROGRESS,
|
||||
ConfigEntryState.NOT_LOADED,
|
||||
)
|
||||
) or entry.state is ConfigEntryState.SETUP_RETRY:
|
||||
) or entry.state == ConfigEntryState.SETUP_RETRY:
|
||||
self.hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||
else:
|
||||
async_dispatcher_send(
|
||||
|
||||
@@ -51,7 +51,7 @@ async def async_setup_entry(
|
||||
entry.data.get(CONF_NAME, entry.title)
|
||||
base_unique_id = entry.unique_id or entry.entry_id
|
||||
|
||||
if device.device_type is DeviceType.Switch:
|
||||
if device.device_type == DeviceType.Switch:
|
||||
entities.append(FluxPowerStateSelect(coordinator.device, entry))
|
||||
if device.operating_modes:
|
||||
entities.append(
|
||||
|
||||
@@ -32,7 +32,7 @@ async def async_setup_entry(
|
||||
entities: list[FluxSwitch | FluxRemoteAccessSwitch | FluxMusicSwitch] = []
|
||||
base_unique_id = entry.unique_id or entry.entry_id
|
||||
|
||||
if coordinator.device.device_type is DeviceType.Switch:
|
||||
if coordinator.device.device_type == DeviceType.Switch:
|
||||
entities.append(FluxSwitch(coordinator, base_unique_id, None))
|
||||
|
||||
if entry.data.get(CONF_REMOTE_ACCESS_HOST):
|
||||
|
||||
@@ -212,7 +212,7 @@ class FroniusSolarNet:
|
||||
inverter_info=_inverter_info,
|
||||
config_entry=self.config_entry,
|
||||
)
|
||||
if self.config_entry.state is ConfigEntryState.LOADED:
|
||||
if self.config_entry.state == ConfigEntryState.LOADED:
|
||||
await _coordinator.async_refresh()
|
||||
else:
|
||||
await _coordinator.async_config_entry_first_refresh()
|
||||
@@ -220,7 +220,7 @@ class FroniusSolarNet:
|
||||
|
||||
# Only for re-scans. Initial setup adds entities
|
||||
# through sensor.async_setup_entry
|
||||
if self.config_entry.state is ConfigEntryState.LOADED:
|
||||
if self.config_entry.state == ConfigEntryState.LOADED:
|
||||
async_dispatcher_send(self.hass, SOLAR_NET_DISCOVERY_NEW, _coordinator)
|
||||
|
||||
_LOGGER.debug(
|
||||
@@ -235,7 +235,7 @@ class FroniusSolarNet:
|
||||
try:
|
||||
_inverter_info = await self.fronius.inverter_info()
|
||||
except FroniusError as err:
|
||||
if self.config_entry.state is ConfigEntryState.LOADED:
|
||||
if self.config_entry.state == ConfigEntryState.LOADED:
|
||||
# During a re-scan we will attempt again as per schedule.
|
||||
_LOGGER.debug("Re-scan failed for %s", self.host)
|
||||
return inverter_infos
|
||||
|
||||
@@ -42,7 +42,7 @@ async def _collect_coordinators(
|
||||
raise HomeAssistantError(f"Device '{target}' not found in device registry")
|
||||
coordinators = list[FullyKioskDataUpdateCoordinator]()
|
||||
for config_entry in config_entries:
|
||||
if config_entry.state is not ConfigEntryState.LOADED:
|
||||
if config_entry.state != ConfigEntryState.LOADED:
|
||||
raise HomeAssistantError(f"{config_entry.title} is not loaded")
|
||||
coordinators.append(config_entry.runtime_data)
|
||||
return coordinators
|
||||
|
||||
@@ -75,7 +75,7 @@ async def async_setup_entry(
|
||||
|
||||
mfg_data = await async_get_manufacturer_data({address})
|
||||
product_type = mfg_data[address].product_type
|
||||
if product_type is ProductType.UNKNOWN:
|
||||
if product_type == ProductType.UNKNOWN:
|
||||
raise ConfigEntryNotReady("Unable to find product type")
|
||||
|
||||
client = Client(get_connection(hass, address), product_type)
|
||||
|
||||
@@ -143,7 +143,7 @@ def _get_entity_descriptions(
|
||||
local_sync = True
|
||||
if (
|
||||
search := data.get(CONF_SEARCH)
|
||||
) or calendar_item.access_role is AccessRole.FREE_BUSY_READER:
|
||||
) or calendar_item.access_role == AccessRole.FREE_BUSY_READER:
|
||||
read_only = True
|
||||
local_sync = False
|
||||
entity_description = GoogleCalendarEntityDescription(
|
||||
@@ -386,14 +386,14 @@ class GoogleCalendarEntity(
|
||||
"""Return True if the event is visible and not declined."""
|
||||
|
||||
if any(
|
||||
attendee.is_self and attendee.response_status is ResponseStatus.DECLINED
|
||||
attendee.is_self and attendee.response_status == ResponseStatus.DECLINED
|
||||
for attendee in event.attendees
|
||||
):
|
||||
return False
|
||||
# Calendar enttiy may be limited to a specific event type
|
||||
if (
|
||||
self.entity_description.event_type is not None
|
||||
and self.entity_description.event_type is not event.event_type
|
||||
and self.entity_description.event_type != event.event_type
|
||||
):
|
||||
return False
|
||||
# Default calendar entity omits the special types but includes all the others
|
||||
|
||||
@@ -247,7 +247,7 @@ class LocationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> SubentryFlowResult:
|
||||
"""Handle the location step."""
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
@@ -225,7 +225,7 @@ class LLMSubentryFlowHandler(ConfigSubentryFlow):
|
||||
) -> SubentryFlowResult:
|
||||
"""Set conversation options."""
|
||||
# abort if entry is not loaded
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
@@ -754,7 +754,7 @@ async def async_prepare_files_for_prompt(
|
||||
config={"http_options": {"timeout": TIMEOUT_MILLIS}},
|
||||
)
|
||||
|
||||
if uploaded_file.state is FileState.FAILED:
|
||||
if uploaded_file.state == FileState.FAILED:
|
||||
raise HomeAssistantError(
|
||||
f"File `{uploaded_file.name}` processing"
|
||||
" failed, reason:"
|
||||
@@ -766,7 +766,7 @@ async def async_prepare_files_for_prompt(
|
||||
tasks = [
|
||||
asyncio.create_task(wait_for_file_processing(part))
|
||||
for part in prompt_parts
|
||||
if part.state is not FileState.ACTIVE
|
||||
if part.state != FileState.ACTIVE
|
||||
]
|
||||
async with asyncio.timeout(TIMEOUT_MILLIS / 1000):
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
@@ -237,7 +237,7 @@ class LocationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> SubentryFlowResult:
|
||||
"""Handle the location step."""
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
@@ -26,7 +26,7 @@ def _get_coordinators(
|
||||
coordinators: dict[str, GrowattCoordinator] = {}
|
||||
|
||||
for entry in hass.config_entries.async_entries(DOMAIN):
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
continue
|
||||
|
||||
for coord in entry.runtime_data.devices.values():
|
||||
|
||||
@@ -247,7 +247,7 @@ class SupervisorOSUpdateEntity(HassioOSEntity, UpdateEntity):
|
||||
def release_url(self) -> str | None:
|
||||
"""URL to the full release notes of the latest version available."""
|
||||
version = AwesomeVersion(self.latest_version)
|
||||
if version.dev or version.strategy is AwesomeVersionStrategy.UNKNOWN:
|
||||
if version.dev or version.strategy == AwesomeVersionStrategy.UNKNOWN:
|
||||
return "https://github.com/home-assistant/operating-system/commits/dev"
|
||||
return (
|
||||
f"https://github.com/home-assistant/operating-system/releases/tag/{version}"
|
||||
@@ -304,7 +304,7 @@ class SupervisorSupervisorUpdateEntity(HassioSupervisorEntity, UpdateEntity):
|
||||
def release_url(self) -> str | None:
|
||||
"""URL to the full release notes of the latest version available."""
|
||||
version = AwesomeVersion(self.latest_version)
|
||||
if version.dev or version.strategy is AwesomeVersionStrategy.UNKNOWN:
|
||||
if version.dev or version.strategy == AwesomeVersionStrategy.UNKNOWN:
|
||||
return "https://github.com/home-assistant/supervisor/commits/main"
|
||||
return f"https://github.com/home-assistant/supervisor/releases/tag/{version}"
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ def _get_controller(hass: HomeAssistant) -> Heos:
|
||||
hass.config_entries.async_entry_for_domain_unique_id(DOMAIN, DOMAIN)
|
||||
)
|
||||
|
||||
if not entry or entry.state is not ConfigEntryState.LOADED:
|
||||
if not entry or not entry.state == ConfigEntryState.LOADED:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN, translation_key="integration_not_loaded"
|
||||
)
|
||||
|
||||
@@ -418,10 +418,10 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
|
||||
otbr_manager = get_otbr_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(otbr_manager)
|
||||
|
||||
if addon_info.state is AddonState.NOT_INSTALLED:
|
||||
if addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return await self.async_step_install_otbr_addon()
|
||||
|
||||
if addon_info.state is AddonState.RUNNING:
|
||||
if addon_info.state == AddonState.RUNNING:
|
||||
await otbr_manager.async_stop_addon()
|
||||
|
||||
return await self.async_step_start_otbr_addon()
|
||||
|
||||
@@ -111,7 +111,7 @@ class WaitingAddonManager(AddonManager):
|
||||
info = None
|
||||
|
||||
# Do not try to uninstall an addon if it is already uninstalled
|
||||
if info is not None and info.state is AddonState.NOT_INSTALLED:
|
||||
if info is not None and info.state == AddonState.NOT_INSTALLED:
|
||||
return
|
||||
|
||||
await self.async_uninstall_addon()
|
||||
@@ -383,7 +383,7 @@ class OptionsFlowHandler(OptionsFlow, ABC):
|
||||
multipan_manager = await get_multiprotocol_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(multipan_manager)
|
||||
|
||||
if addon_info.state is AddonState.NOT_INSTALLED:
|
||||
if addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return await self.async_step_addon_not_installed()
|
||||
return await self.async_step_addon_installed()
|
||||
|
||||
@@ -691,10 +691,10 @@ class OptionsFlowHandler(OptionsFlow, ABC):
|
||||
flasher_manager = get_flasher_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(flasher_manager)
|
||||
|
||||
if addon_info.state is AddonState.NOT_INSTALLED:
|
||||
if addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return await self.async_step_install_flasher_addon()
|
||||
|
||||
if addon_info.state is AddonState.NOT_RUNNING:
|
||||
if addon_info.state == AddonState.NOT_RUNNING:
|
||||
return await self.async_step_configure_flasher_addon()
|
||||
|
||||
# If the addon is already installed and running, fail
|
||||
@@ -907,7 +907,7 @@ async def check_multi_pan_addon(hass: HomeAssistant) -> None:
|
||||
# Request the addon to start if it's not started
|
||||
# `async_start_addon` returns as soon as the start request has been sent
|
||||
# and does not wait for the addon to be started, so we raise below
|
||||
if addon_info.state is AddonState.NOT_RUNNING:
|
||||
if addon_info.state == AddonState.NOT_RUNNING:
|
||||
await multipan_manager.async_start_addon()
|
||||
|
||||
if addon_info.state not in (AddonState.NOT_INSTALLED, AddonState.RUNNING):
|
||||
@@ -927,7 +927,7 @@ async def multi_pan_addon_using_device(hass: HomeAssistant, device_path: str) ->
|
||||
multipan_manager = await get_multiprotocol_addon_manager(hass)
|
||||
addon_info: AddonInfo = await multipan_manager.async_get_addon_info()
|
||||
|
||||
if addon_info.state is not AddonState.RUNNING:
|
||||
if addon_info.state != AddonState.RUNNING:
|
||||
return False
|
||||
|
||||
if addon_info.options["device"] != device_path:
|
||||
|
||||
@@ -124,7 +124,7 @@ class OwningAddon:
|
||||
except AddonError:
|
||||
return False
|
||||
else:
|
||||
return addon_info.state is AddonState.RUNNING
|
||||
return addon_info.state == AddonState.RUNNING
|
||||
|
||||
@asynccontextmanager
|
||||
async def temporarily_stop(self, hass: HomeAssistant) -> AsyncGenerator[None]:
|
||||
@@ -137,7 +137,7 @@ class OwningAddon:
|
||||
yield
|
||||
return
|
||||
|
||||
if addon_info.state is not AddonState.RUNNING:
|
||||
if addon_info.state != AddonState.RUNNING:
|
||||
yield
|
||||
return
|
||||
|
||||
@@ -173,7 +173,7 @@ class OwningIntegration:
|
||||
yield
|
||||
return
|
||||
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
yield
|
||||
return
|
||||
|
||||
@@ -213,7 +213,7 @@ async def get_otbr_addon_firmware_info(
|
||||
except AddonError:
|
||||
return None
|
||||
|
||||
if otbr_addon_info.state is AddonState.NOT_INSTALLED:
|
||||
if otbr_addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return None
|
||||
|
||||
if (otbr_path := otbr_addon_info.options.get("device")) is None:
|
||||
@@ -238,7 +238,7 @@ async def get_z2m_addon_firmware_info(
|
||||
except AddonError:
|
||||
return None
|
||||
|
||||
if z2m_addon_info.state is AddonState.NOT_INSTALLED:
|
||||
if z2m_addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return None
|
||||
|
||||
serial = z2m_addon_info.options.get("serial")
|
||||
@@ -286,7 +286,7 @@ async def guess_hardware_owners(
|
||||
except AddonError:
|
||||
pass
|
||||
else:
|
||||
if multipan_addon_info.state is not AddonState.NOT_INSTALLED:
|
||||
if multipan_addon_info.state != AddonState.NOT_INSTALLED:
|
||||
multipan_path = multipan_addon_info.options.get("device")
|
||||
|
||||
if multipan_path is not None:
|
||||
|
||||
@@ -122,7 +122,7 @@ class HomeeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
existing_entry = await self.async_set_unique_id(self._name)
|
||||
if (
|
||||
existing_entry
|
||||
and existing_entry.state is ConfigEntryState.LOADED
|
||||
and existing_entry.state == ConfigEntryState.LOADED
|
||||
and existing_entry.runtime_data.connected
|
||||
and existing_entry.data[CONF_HOST] != self._host
|
||||
):
|
||||
|
||||
@@ -298,7 +298,7 @@ class HKDevice:
|
||||
# yet.
|
||||
attempts = None if self.hass.state is CoreState.running else 1
|
||||
if (
|
||||
transport is Transport.BLE
|
||||
transport == Transport.BLE
|
||||
and pairing.accessories
|
||||
and pairing.accessories.has_aid(1)
|
||||
):
|
||||
@@ -328,7 +328,7 @@ class HKDevice:
|
||||
)
|
||||
entry.async_on_unload(self._async_cancel_subscription_timer)
|
||||
|
||||
if transport is not Transport.BLE:
|
||||
if transport != Transport.BLE:
|
||||
# Although async_populate_accessories_state fetched the accessory database,
|
||||
# the /accessories endpoint may return cached values from the accessory's
|
||||
# perspective. For example, Ecobee thermostats may report stale temperature
|
||||
@@ -349,7 +349,7 @@ class HKDevice:
|
||||
|
||||
await self.async_process_entity_map()
|
||||
|
||||
if transport is not Transport.BLE:
|
||||
if transport != Transport.BLE:
|
||||
# Start regular polling after entity map is processed
|
||||
self._async_start_polling()
|
||||
|
||||
@@ -359,7 +359,7 @@ class HKDevice:
|
||||
|
||||
self.async_set_available_state(self.pairing.is_available)
|
||||
|
||||
if transport is Transport.BLE:
|
||||
if transport == Transport.BLE:
|
||||
# If we are using BLE, we need to periodically check of the
|
||||
# BLE device is available since we won't get callbacks
|
||||
# when it goes away since we HomeKit supports disconnected
|
||||
@@ -420,7 +420,7 @@ class HKDevice:
|
||||
identifiers.add((IDENTIFIER_SERIAL_NUMBER, accessory.serial_number))
|
||||
|
||||
connections: set[tuple[str, str]] = set()
|
||||
if self.pairing.transport is Transport.BLE and (
|
||||
if self.pairing.transport == Transport.BLE and (
|
||||
discovery := self.pairing.controller.discoveries.get(
|
||||
normalize_hkid(self.unique_id)
|
||||
)
|
||||
@@ -622,7 +622,7 @@ class HKDevice:
|
||||
current_unique_id.add((accessory.aid, service.iid, None))
|
||||
|
||||
for char in service.characteristics:
|
||||
if self.pairing.transport is not Transport.BLE:
|
||||
if self.pairing.transport != Transport.BLE:
|
||||
if char.type == CharacteristicsTypes.THREAD_CONTROL_POINT:
|
||||
continue
|
||||
|
||||
@@ -1057,7 +1057,7 @@ class HKDevice:
|
||||
@property
|
||||
def is_unprovisioned_thread_device(self) -> bool:
|
||||
"""Is this a thread capable device not connected by CoAP."""
|
||||
if self.pairing.controller.transport_type is not TransportType.BLE:
|
||||
if self.pairing.controller.transport_type != TransportType.BLE:
|
||||
return False
|
||||
|
||||
if not self.entity_map.aid(1).services.first(
|
||||
@@ -1069,7 +1069,7 @@ class HKDevice:
|
||||
|
||||
async def async_thread_provision(self) -> None:
|
||||
"""Migrate a HomeKit pairing to CoAP (Thread)."""
|
||||
if self.pairing.controller.transport_type is TransportType.COAP:
|
||||
if self.pairing.controller.transport_type == TransportType.COAP:
|
||||
raise HomeAssistantError("Already connected to a thread network")
|
||||
|
||||
if not (dataset := await async_get_preferred_dataset(self.hass)):
|
||||
|
||||
@@ -700,7 +700,7 @@ async def async_setup_entry(
|
||||
|
||||
@callback
|
||||
def async_add_accessory(accessory: Accessory) -> bool:
|
||||
if conn.pairing.transport is not Transport.BLE:
|
||||
if conn.pairing.transport != Transport.BLE:
|
||||
return False
|
||||
|
||||
accessory_info = accessory.services.first(
|
||||
|
||||
@@ -69,7 +69,7 @@ class HusqvarnaAutomowerBleConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
await async_get_manufacturer_data({discovery_info.address})
|
||||
)[discovery_info.address]
|
||||
|
||||
if manufacturer_data.product_type is not ProductType.MOWER:
|
||||
if manufacturer_data.product_type != ProductType.MOWER:
|
||||
LOGGER.debug(
|
||||
"Unsupported device: %s (%s)", manufacturer_data, discovery_info
|
||||
)
|
||||
|
||||
@@ -567,7 +567,7 @@ class IntegrationSensor(RestoreSensor):
|
||||
assert old_timestamp is not None
|
||||
elapsed_seconds = Decimal(
|
||||
(new_timestamp - old_timestamp).total_seconds()
|
||||
if self._last_integration_trigger is _IntegrationTrigger.StateEvent
|
||||
if self._last_integration_trigger == _IntegrationTrigger.StateEvent
|
||||
else (new_timestamp - self._last_integration_time).total_seconds()
|
||||
)
|
||||
|
||||
|
||||
@@ -192,11 +192,11 @@ async def async_update_options(
|
||||
current_control_mode = fireplace.control_mode
|
||||
|
||||
# Only update modes that actually changed
|
||||
if new_read_mode is not current_read_mode:
|
||||
if new_read_mode != current_read_mode:
|
||||
LOGGER.debug("Updating read mode: %s -> %s", current_read_mode, new_read_mode)
|
||||
await fireplace.set_read_mode(new_read_mode)
|
||||
|
||||
if new_control_mode is not current_control_mode:
|
||||
if new_control_mode != current_control_mode:
|
||||
LOGGER.debug(
|
||||
"Updating control mode: %s -> %s", current_control_mode, new_control_mode
|
||||
)
|
||||
|
||||
@@ -126,19 +126,19 @@ class IottyShutter(IottyEntity, CoverEntity):
|
||||
self._iotty_device.percentage,
|
||||
)
|
||||
return (
|
||||
self._iotty_device.status is ShutterState.STATIONARY
|
||||
self._iotty_device.status == ShutterState.STATIONARY
|
||||
and self._iotty_device.percentage == 0
|
||||
)
|
||||
|
||||
@property
|
||||
def is_opening(self) -> bool:
|
||||
"""Return true if the Shutter is opening."""
|
||||
return self._iotty_device.status is ShutterState.OPENING
|
||||
return self._iotty_device.status == ShutterState.OPENING
|
||||
|
||||
@property
|
||||
def is_closing(self) -> bool:
|
||||
"""Return true if the Shutter is closing."""
|
||||
return self._iotty_device.status is ShutterState.CLOSING
|
||||
return self._iotty_device.status == ShutterState.CLOSING
|
||||
|
||||
@property
|
||||
def supported_features(self) -> CoverEntityFeature:
|
||||
|
||||
@@ -380,7 +380,7 @@ class _KnxClimate(ClimateEntity, _KnxEntityBase):
|
||||
self._attr_fan_modes = [fan_zero_mode, FAN_LOW, FAN_HIGH]
|
||||
elif fan_max_step == 1:
|
||||
self._attr_fan_modes = [fan_zero_mode, FAN_ON]
|
||||
elif device.fan_speed_mode is FanSpeedMode.STEP:
|
||||
elif device.fan_speed_mode == FanSpeedMode.STEP:
|
||||
self._attr_fan_modes = [fan_zero_mode] + [
|
||||
str(i) for i in range(1, fan_max_step + 1)
|
||||
]
|
||||
@@ -550,7 +550,7 @@ class _KnxClimate(ClimateEntity, _KnxEntityBase):
|
||||
if not fan_speed or self._attr_fan_modes is None:
|
||||
return self.fan_zero_mode
|
||||
|
||||
if self._device.fan_speed_mode is FanSpeedMode.STEP:
|
||||
if self._device.fan_speed_mode == FanSpeedMode.STEP:
|
||||
return self._attr_fan_modes[fan_speed]
|
||||
|
||||
# Find the closest fan mode percentage
|
||||
@@ -570,7 +570,7 @@ class _KnxClimate(ClimateEntity, _KnxEntityBase):
|
||||
|
||||
fan_mode_index = self._attr_fan_modes.index(fan_mode)
|
||||
|
||||
if self._device.fan_speed_mode is FanSpeedMode.STEP:
|
||||
if self._device.fan_speed_mode == FanSpeedMode.STEP:
|
||||
await self._device.set_fan_speed(fan_mode_index)
|
||||
return
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ class KNXModule:
|
||||
|
||||
def connection_state_changed_cb(self, state: XknxConnectionState) -> None:
|
||||
"""Call invoked after a KNX connection state change was received."""
|
||||
self.connected = state is XknxConnectionState.CONNECTED
|
||||
self.connected = state == XknxConnectionState.CONNECTED
|
||||
for device in self.xknx.devices:
|
||||
device.after_update()
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ def async_host_event_received(
|
||||
):
|
||||
_LOGGER.info('The connection to host "%s" has been lost', config_entry.title)
|
||||
hass.async_create_task(reload_config_entry())
|
||||
elif event is LcnEvent.BUS_DISCONNECTED:
|
||||
elif event == LcnEvent.BUS_DISCONNECTED:
|
||||
_LOGGER.info(
|
||||
'The connection to the LCN bus via host "%s" has been disconnected',
|
||||
config_entry.title,
|
||||
@@ -296,7 +296,7 @@ def _async_fire_access_control_event(
|
||||
if device is not None:
|
||||
event_data.update({CONF_DEVICE_ID: device.id})
|
||||
|
||||
if inp.periphery is pypck.lcn_defs.AccessControlPeriphery.TRANSMITTER:
|
||||
if inp.periphery == pypck.lcn_defs.AccessControlPeriphery.TRANSMITTER:
|
||||
event_data.update(
|
||||
{
|
||||
"level": inp.level,
|
||||
@@ -317,7 +317,7 @@ def _async_fire_send_keys_event(
|
||||
) -> None:
|
||||
"""Fire send_keys event."""
|
||||
for table, action in enumerate(inp.actions):
|
||||
if action is pypck.lcn_defs.SendKeyCommand.DONTSEND:
|
||||
if action == pypck.lcn_defs.SendKeyCommand.DONTSEND:
|
||||
continue
|
||||
|
||||
for key, selected in enumerate(inp.keys):
|
||||
|
||||
@@ -116,7 +116,7 @@ class LcnClimate(LcnEntity, ClimateEntity):
|
||||
# Config schema only allows for:
|
||||
# UnitOfTemperature.CELSIUS and
|
||||
# UnitOfTemperature.FAHRENHEIT
|
||||
if self.unit is pypck.lcn_defs.VarUnit.FAHRENHEIT:
|
||||
if self.unit == pypck.lcn_defs.VarUnit.FAHRENHEIT:
|
||||
return UnitOfTemperature.FAHRENHEIT
|
||||
return UnitOfTemperature.CELSIUS
|
||||
|
||||
@@ -188,11 +188,11 @@ class LcnClimate(LcnEntity, ClimateEntity):
|
||||
if not isinstance(input_obj, pypck.inputs.ModStatusVar):
|
||||
return
|
||||
self._attr_available = True
|
||||
if input_obj.get_var() is self.variable:
|
||||
if input_obj.get_var() == self.variable:
|
||||
self._attr_current_temperature = float(
|
||||
input_obj.get_value().to_var_unit(self.unit)
|
||||
)
|
||||
elif input_obj.get_var() is self.setpoint:
|
||||
elif input_obj.get_var() == self.setpoint:
|
||||
self._is_on = not input_obj.get_value().is_locked_regulator()
|
||||
if self._is_on:
|
||||
self._attr_target_temperature = float(
|
||||
|
||||
@@ -191,7 +191,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
|
||||
)
|
||||
)
|
||||
|
||||
if self.positioning_mode is not pypck.lcn_defs.MotorPositioningMode.NONE:
|
||||
if self.positioning_mode != pypck.lcn_defs.MotorPositioningMode.NONE:
|
||||
self._attr_supported_features |= CoverEntityFeature.SET_POSITION
|
||||
|
||||
self.motor = pypck.lcn_defs.MotorPort[config[CONF_DOMAIN_DATA][CONF_MOTOR]]
|
||||
@@ -267,7 +267,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
|
||||
| None,
|
||||
]
|
||||
] = [self.device_connection.request_status_relays(SCAN_INTERVAL.seconds)]
|
||||
if self.positioning_mode is pypck.lcn_defs.MotorPositioningMode.BS4:
|
||||
if self.positioning_mode == pypck.lcn_defs.MotorPositioningMode.BS4:
|
||||
coros.append(
|
||||
self.device_connection.request_status_motor_position(
|
||||
self.motor, self.positioning_mode, SCAN_INTERVAL.seconds
|
||||
@@ -282,7 +282,7 @@ class LcnRelayCover(LcnEntity, CoverEntity):
|
||||
self._attr_is_opening = input_obj.is_opening(self.motor.value)
|
||||
self._attr_is_closing = input_obj.is_closing(self.motor.value)
|
||||
|
||||
if self.positioning_mode is pypck.lcn_defs.MotorPositioningMode.NONE:
|
||||
if self.positioning_mode == pypck.lcn_defs.MotorPositioningMode.NONE:
|
||||
self._attr_is_closed = input_obj.is_assumed_closed(self.motor.value)
|
||||
self.async_write_ha_state()
|
||||
elif (
|
||||
|
||||
@@ -144,7 +144,7 @@ class LcnVariableSensor(LcnEntity, SensorEntity):
|
||||
"""Set sensor value when LCN input object (command) is received."""
|
||||
if (
|
||||
not isinstance(input_obj, pypck.inputs.ModStatusVar)
|
||||
or input_obj.get_var() is not self.variable
|
||||
or input_obj.get_var() != self.variable
|
||||
):
|
||||
return
|
||||
self._attr_available = True
|
||||
|
||||
@@ -330,7 +330,7 @@ class SendKeys(LcnServiceCall):
|
||||
|
||||
if (delay_time := service.data[CONF_TIME]) != 0:
|
||||
hit = pypck.lcn_defs.SendKeyCommand.HIT
|
||||
if pypck.lcn_defs.SendKeyCommand[service.data[CONF_STATE]] is not hit:
|
||||
if pypck.lcn_defs.SendKeyCommand[service.data[CONF_STATE]] != hit:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_send_keys_action",
|
||||
|
||||
@@ -200,7 +200,7 @@ class LcnRegulatorLockSwitch(LcnEntity, SwitchEntity):
|
||||
"""Set switch state when LCN input object (command) is received."""
|
||||
if (
|
||||
not isinstance(input_obj, pypck.inputs.ModStatusVar)
|
||||
or input_obj.get_var() is not self.setpoint_variable
|
||||
or input_obj.get_var() != self.setpoint_variable
|
||||
):
|
||||
return
|
||||
self._attr_available = True
|
||||
|
||||
@@ -54,4 +54,4 @@ class LutronOccupancySensor(LutronDevice, BinarySensorEntity):
|
||||
|
||||
def _update_attrs(self) -> None:
|
||||
"""Update the state attributes."""
|
||||
self._attr_is_on = self._lutron_device.state is OccupancyGroup.State.OCCUPIED
|
||||
self._attr_is_on = self._lutron_device.state == OccupancyGroup.State.OCCUPIED
|
||||
|
||||
@@ -79,7 +79,7 @@ class LutronCasetaShade(LutronCasetaUpdatableEntity, CoverEntity):
|
||||
"""Stop the cover."""
|
||||
# Send appropriate directional command before stop to ensure it works correctly
|
||||
# Use tracked direction if moving, otherwise use position-based heuristic
|
||||
if self._movement_direction is ShadeMovementDirection.OPENING or (
|
||||
if self._movement_direction == ShadeMovementDirection.OPENING or (
|
||||
self._movement_direction in (ShadeMovementDirection.STOPPED, None)
|
||||
and self.current_cover_position >= 50
|
||||
):
|
||||
|
||||
@@ -179,13 +179,13 @@ async def _client_listen(
|
||||
try:
|
||||
await matter_client.start_listening(init_ready)
|
||||
except MatterError as err:
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
raise
|
||||
LOGGER.error("Failed to listen: %s", err)
|
||||
except Exception as err:
|
||||
# We need to guard against unknown exceptions to not crash this task.
|
||||
LOGGER.exception("Unexpected exception: %s", err)
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
raise
|
||||
|
||||
if not hass.is_stopping:
|
||||
@@ -293,14 +293,14 @@ async def _async_ensure_addon_running(
|
||||
|
||||
addon_state = addon_info.state
|
||||
|
||||
if addon_state is AddonState.NOT_INSTALLED:
|
||||
if addon_state == AddonState.NOT_INSTALLED:
|
||||
addon_manager.async_schedule_install_setup_addon(
|
||||
addon_info.options,
|
||||
catch_error=True,
|
||||
)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
if addon_state is AddonState.NOT_RUNNING:
|
||||
if addon_state == AddonState.NOT_RUNNING:
|
||||
addon_manager.async_schedule_start_addon(catch_error=True)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
|
||||
@@ -288,10 +288,10 @@ class MatterConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
addon_info = await self._async_get_addon_info()
|
||||
|
||||
if addon_info.state is AddonState.RUNNING:
|
||||
if addon_info.state == AddonState.RUNNING:
|
||||
return await self.async_step_finish_addon_setup()
|
||||
|
||||
if addon_info.state is AddonState.NOT_RUNNING:
|
||||
if addon_info.state == AddonState.NOT_RUNNING:
|
||||
return await self.async_step_start_addon()
|
||||
|
||||
return await self.async_step_install_addon()
|
||||
|
||||
@@ -173,7 +173,7 @@ class MatterUpdate(MatterEntity, UpdateEntity):
|
||||
release_notes = ""
|
||||
|
||||
# insert extra heavy warning case the update is not from the main net
|
||||
if self._software_update.update_source is not UpdateSource.MAIN_NET_DCL:
|
||||
if self._software_update.update_source != UpdateSource.MAIN_NET_DCL:
|
||||
release_notes += (
|
||||
"\n\n<ha-alert alert-type='warning'>"
|
||||
"Update provided by "
|
||||
|
||||
@@ -437,7 +437,7 @@ def _get_media_event_data(
|
||||
if (
|
||||
not config_entry_id
|
||||
or not (entry := hass.config_entries.async_get_entry(config_entry_id))
|
||||
or entry.state is not ConfigEntryState.LOADED
|
||||
or entry.state != ConfigEntryState.LOADED
|
||||
):
|
||||
return {}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ class MotionEyeMediaSource(MediaSource):
|
||||
def _get_config_or_raise(self, config_id: str) -> MotionEyeConfigEntry:
|
||||
"""Get a config entry from a URL."""
|
||||
entry = self.hass.config_entries.async_get_entry(config_id)
|
||||
if not entry or entry.state is not ConfigEntryState.LOADED:
|
||||
if not entry or entry.state != ConfigEntryState.LOADED:
|
||||
raise MediaSourceError(f"Unable to find config entry with id: {config_id}")
|
||||
return entry
|
||||
|
||||
|
||||
@@ -4154,11 +4154,11 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
description_placeholders={"addon": self._addon_manager.addon_name},
|
||||
) from err
|
||||
|
||||
if addon_info.state is AddonState.RUNNING:
|
||||
if addon_info.state == AddonState.RUNNING:
|
||||
# Finish setup using discovery info
|
||||
return await self.async_step_setup_entry_from_discovery()
|
||||
|
||||
if addon_info.state is AddonState.NOT_RUNNING:
|
||||
if addon_info.state == AddonState.NOT_RUNNING:
|
||||
return await self.async_step_start_addon()
|
||||
|
||||
# Install the add-on and start it
|
||||
|
||||
@@ -219,7 +219,7 @@ async def async_wait_for_mqtt_client(hass: HomeAssistant) -> bool:
|
||||
return False
|
||||
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
if entry.state is ConfigEntryState.LOADED:
|
||||
if entry.state == ConfigEntryState.LOADED:
|
||||
return True
|
||||
|
||||
state_reached_future: asyncio.Future[bool]
|
||||
|
||||
@@ -265,12 +265,12 @@ async def _client_listen(
|
||||
try:
|
||||
await mass.start_listening(init_ready)
|
||||
except MusicAssistantError as err:
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
raise
|
||||
LOGGER.error("Failed to listen: %s", err)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
# We need to guard against unknown exceptions to not crash this task.
|
||||
if entry.state is not ConfigEntryState.LOADED:
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
raise
|
||||
LOGGER.exception("Unexpected exception: %s", err)
|
||||
|
||||
|
||||
@@ -81,17 +81,17 @@ class MySensorsCover(MySensorsChildEntity, CoverEntity):
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
"""Return True if the cover is closed."""
|
||||
return self.get_cover_state() is CoverState.CLOSED
|
||||
return self.get_cover_state() == CoverState.CLOSED
|
||||
|
||||
@property
|
||||
def is_closing(self) -> bool:
|
||||
"""Return True if the cover is closing."""
|
||||
return self.get_cover_state() is CoverState.CLOSING
|
||||
return self.get_cover_state() == CoverState.CLOSING
|
||||
|
||||
@property
|
||||
def is_opening(self) -> bool:
|
||||
"""Return True if the cover is opening."""
|
||||
return self.get_cover_state() is CoverState.OPENING
|
||||
return self.get_cover_state() == CoverState.OPENING
|
||||
|
||||
@property
|
||||
def current_cover_position(self) -> int | None:
|
||||
|
||||
@@ -144,7 +144,7 @@ class MyUplinkDevicePointBinarySensor(MyUplinkEntity, BinarySensorEntity):
|
||||
"""Return device data availability."""
|
||||
return super().available and (
|
||||
self.coordinator.data.devices[self.device_id].connectionState
|
||||
is DeviceConnectionState.Connected
|
||||
== DeviceConnectionState.Connected
|
||||
)
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ class MyUplinkDeviceBinarySensor(MyUplinkEntity, BinarySensorEntity):
|
||||
"""Binary sensor state value."""
|
||||
return (
|
||||
self.coordinator.data.devices[self.device_id].connectionState
|
||||
is DeviceConnectionState.Connected
|
||||
== DeviceConnectionState.Connected
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ def async_get_client_for_service_call(
|
||||
if device_entry := device_registry.async_get(device_id):
|
||||
for entry_id in device_entry.config_entries:
|
||||
if entry := hass.config_entries.async_get_entry(entry_id):
|
||||
if entry.domain == DOMAIN and entry.state is ConfigEntryState.LOADED:
|
||||
if entry.domain == DOMAIN and entry.state == ConfigEntryState.LOADED:
|
||||
return cast(OctoprintConfigEntry, entry).runtime_data.octoprint
|
||||
|
||||
raise ServiceValidationError(
|
||||
|
||||
@@ -259,7 +259,7 @@ class OllamaSubentryFlowHandler(ConfigSubentryFlow):
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> SubentryFlowResult:
|
||||
"""Handle model selection and configuration step."""
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
if user_input is None:
|
||||
|
||||
@@ -160,6 +160,6 @@ class ChannelMutingCoordinator(DataUpdateCoordinator[ChannelMutingData]):
|
||||
self._desired = {
|
||||
channel: desired
|
||||
for channel, desired in self._desired.items()
|
||||
if self.data[channel] is not desired
|
||||
if self.data[channel] != desired
|
||||
}
|
||||
self.async_set_updated_data(self.data)
|
||||
|
||||
@@ -197,7 +197,7 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
|
||||
name = manager.info.model_name
|
||||
identifier = manager.info.identifier
|
||||
self._attr_name = f"{name}{' ' + ZONES[zone] if zone is not Zone.MAIN else ''}"
|
||||
self._attr_name = f"{name}{' ' + ZONES[zone] if zone != Zone.MAIN else ''}"
|
||||
self._attr_unique_id = f"{identifier}_{zone.value}"
|
||||
|
||||
self._volume_resolution = volume_resolution
|
||||
@@ -226,7 +226,7 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
self._attr_sound_mode_list = list(self._rev_sound_mode_mapping)
|
||||
|
||||
self._attr_supported_features = SUPPORTED_FEATURES_BASE
|
||||
if zone is Zone.MAIN:
|
||||
if zone == Zone.MAIN:
|
||||
self._attr_supported_features |= SUPPORTED_FEATURES_VOLUME
|
||||
self._supports_volume = True
|
||||
self._attr_supported_features |= MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
||||
@@ -259,7 +259,7 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
await self._manager.write(query.TunerPreset(self._zone))
|
||||
if self._supports_sound_mode is not None:
|
||||
await self._manager.write(query.ListeningMode(self._zone))
|
||||
if self._zone is Zone.MAIN:
|
||||
if self._zone == Zone.MAIN:
|
||||
await self._manager.write(query.HDMIOutput())
|
||||
await self._manager.write(query.AudioInformation())
|
||||
await self._manager.write(query.VideoInformation())
|
||||
@@ -386,7 +386,7 @@ class OnkyoMediaPlayer(MediaPlayerEntity):
|
||||
self._attr_volume_level = min(1, volume_level)
|
||||
|
||||
case status.Muting(param=muting):
|
||||
self._attr_is_volume_muted = bool(muting is status.Muting.Param.ON)
|
||||
self._attr_is_volume_muted = bool(muting == status.Muting.Param.ON)
|
||||
|
||||
case status.InputSource(param=source):
|
||||
if source in self._source_mapping:
|
||||
|
||||
@@ -89,6 +89,6 @@ class OnkyoChannelMutingSwitch(
|
||||
"""Handle updated data from the coordinator."""
|
||||
value = self.coordinator.data.get(self._channel)
|
||||
self._attr_is_on = (
|
||||
None if value is None else value is status.ChannelMuting.Param.ON
|
||||
None if value is None else value == status.ChannelMuting.Param.ON
|
||||
)
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@@ -127,8 +127,8 @@ class EventManager:
|
||||
def started(self) -> bool:
|
||||
"""Return True if event manager is started."""
|
||||
return (
|
||||
self.webhook_manager.state is WebHookManagerState.STARTED
|
||||
or self.pullpoint_manager.state is PullPointManagerState.STARTED
|
||||
self.webhook_manager.state == WebHookManagerState.STARTED
|
||||
or self.pullpoint_manager.state == PullPointManagerState.STARTED
|
||||
)
|
||||
|
||||
@callback
|
||||
@@ -260,7 +260,7 @@ class EventManager:
|
||||
@callback
|
||||
def async_webhook_failed(self) -> None:
|
||||
"""Mark webhook as failed."""
|
||||
if self.pullpoint_manager.state is not PullPointManagerState.PAUSED:
|
||||
if self.pullpoint_manager.state != PullPointManagerState.PAUSED:
|
||||
return
|
||||
LOGGER.debug("%s: Switching to PullPoint for events", self.name)
|
||||
self.pullpoint_manager.async_resume()
|
||||
@@ -268,7 +268,7 @@ class EventManager:
|
||||
@callback
|
||||
def async_webhook_working(self) -> None:
|
||||
"""Mark webhook as working."""
|
||||
if self.pullpoint_manager.state is not PullPointManagerState.STARTED:
|
||||
if self.pullpoint_manager.state != PullPointManagerState.STARTED:
|
||||
return
|
||||
LOGGER.debug("%s: Switching to webhook for events", self.name)
|
||||
self.pullpoint_manager.async_pause()
|
||||
@@ -308,7 +308,7 @@ class PullPointManager:
|
||||
|
||||
async def async_start(self) -> bool:
|
||||
"""Start pullpoint subscription."""
|
||||
assert self.state is PullPointManagerState.STOPPED, (
|
||||
assert self.state == PullPointManagerState.STOPPED, (
|
||||
"PullPoint manager already started"
|
||||
)
|
||||
LOGGER.debug("%s: Starting PullPoint manager", self._name)
|
||||
@@ -462,7 +462,7 @@ class PullPointManager:
|
||||
finally:
|
||||
self.async_schedule_pull_messages(next_pull_delay)
|
||||
|
||||
if self.state is not PullPointManagerState.STARTED:
|
||||
if self.state != PullPointManagerState.STARTED:
|
||||
# If the webhook became started working during the long poll,
|
||||
# and we got paused, our data is stale and we should not process it.
|
||||
LOGGER.debug(
|
||||
@@ -507,7 +507,7 @@ class PullPointManager:
|
||||
Must not check if the webhook is working.
|
||||
"""
|
||||
self.async_cancel_pull_messages()
|
||||
if self.state is not PullPointManagerState.STARTED:
|
||||
if self.state != PullPointManagerState.STARTED:
|
||||
return
|
||||
if self._pullpoint_manager:
|
||||
when = delay if delay is not None else PULLPOINT_COOLDOWN_TIME
|
||||
@@ -566,7 +566,7 @@ class WebHookManager:
|
||||
async def async_start(self) -> bool:
|
||||
"""Start polling events."""
|
||||
LOGGER.debug("%s: Starting webhook manager", self._name)
|
||||
assert self.state is WebHookManagerState.STOPPED, (
|
||||
assert self.state == WebHookManagerState.STOPPED, (
|
||||
"Webhook manager already started"
|
||||
)
|
||||
assert self._webhook_url is None, "Webhook already registered"
|
||||
|
||||
@@ -141,7 +141,7 @@ class ConversationFlowHandler(OpenRouterSubentryFlowHandler):
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage conversation agent configuration."""
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
if user_input is not None:
|
||||
@@ -256,7 +256,7 @@ class AITaskDataFlowHandler(OpenRouterSubentryFlowHandler):
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage AI task configuration."""
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
if user_input is not None:
|
||||
|
||||
@@ -255,7 +255,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage initial options."""
|
||||
# abort if entry is not loaded
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
options = self.options
|
||||
@@ -710,7 +710,7 @@ class OpenAISubentrySTTFlowHandler(ConfigSubentryFlow):
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage initial options."""
|
||||
# abort if entry is not loaded
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
options = self.options
|
||||
@@ -799,7 +799,7 @@ class OpenAISubentryTTSFlowHandler(ConfigSubentryFlow):
|
||||
) -> SubentryFlowResult:
|
||||
"""Manage initial options."""
|
||||
# abort if entry is not loaded
|
||||
if self._get_entry().state is not ConfigEntryState.LOADED:
|
||||
if self._get_entry().state != ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
options = self.options
|
||||
|
||||
@@ -193,12 +193,12 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, OpowerData]]):
|
||||
)
|
||||
consumption_unit_class = (
|
||||
EnergyConverter.UNIT_CLASS
|
||||
if account.meter_type is MeterType.ELEC
|
||||
if account.meter_type == MeterType.ELEC
|
||||
else VolumeConverter.UNIT_CLASS
|
||||
)
|
||||
consumption_unit = (
|
||||
UnitOfEnergy.KILO_WATT_HOUR
|
||||
if account.meter_type is MeterType.ELEC
|
||||
if account.meter_type == MeterType.ELEC
|
||||
else UnitOfVolume.CENTUM_CUBIC_FEET
|
||||
)
|
||||
consumption_metadata = StatisticMetaData(
|
||||
@@ -553,7 +553,7 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, OpowerData]]):
|
||||
_LOGGER.error("Error getting monthly cost reads: %s", err)
|
||||
raise
|
||||
_LOGGER.debug("Got %s monthly cost reads", len(cost_reads))
|
||||
if account.read_resolution is ReadResolution.BILLING:
|
||||
if account.read_resolution == ReadResolution.BILLING:
|
||||
return cost_reads
|
||||
|
||||
if start_time is None:
|
||||
@@ -573,7 +573,7 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, OpowerData]]):
|
||||
raise
|
||||
_LOGGER.debug("Got %s daily cost reads", len(daily_cost_reads))
|
||||
_update_with_finer_cost_reads(cost_reads, daily_cost_reads)
|
||||
if account.read_resolution is ReadResolution.DAY:
|
||||
if account.read_resolution == ReadResolution.DAY:
|
||||
return cost_reads
|
||||
|
||||
if start_time is None:
|
||||
|
||||
@@ -233,13 +233,13 @@ async def async_setup_entry(
|
||||
)
|
||||
sensors: tuple[OpowerEntityDescription, ...] = COMMON_SENSORS
|
||||
if (
|
||||
account.meter_type is MeterType.ELEC
|
||||
account.meter_type == MeterType.ELEC
|
||||
and forecast is not None
|
||||
and forecast.unit_of_measure is UnitOfMeasure.KWH
|
||||
and forecast.unit_of_measure == UnitOfMeasure.KWH
|
||||
):
|
||||
sensors += ELEC_SENSORS
|
||||
elif (
|
||||
account.meter_type is MeterType.GAS
|
||||
account.meter_type == MeterType.GAS
|
||||
and forecast is not None
|
||||
and forecast.unit_of_measure in [UnitOfMeasure.THERM, UnitOfMeasure.CCF]
|
||||
):
|
||||
|
||||
@@ -213,7 +213,7 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
or current_url.port == config["port"]
|
||||
):
|
||||
# Reload the entry since OTBR has restarted
|
||||
if current_entry.state is ConfigEntryState.LOADED:
|
||||
if current_entry.state == ConfigEntryState.LOADED:
|
||||
assert current_entry.unique_id is not None
|
||||
await self.hass.config_entries.async_reload(
|
||||
current_entry.entry_id
|
||||
|
||||
@@ -130,7 +130,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
options=[
|
||||
item.value.lower() for item in StatusType if item is not StatusType.UNKNOWN
|
||||
item.value.lower() for item in StatusType if item != StatusType.UNKNOWN
|
||||
],
|
||||
value_fn=(
|
||||
lambda data: (
|
||||
@@ -138,7 +138,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
if (
|
||||
data.database is not None
|
||||
and data.database.status is not None
|
||||
and data.database.status is not StatusType.UNKNOWN
|
||||
and data.database.status != StatusType.UNKNOWN
|
||||
)
|
||||
else None
|
||||
)
|
||||
@@ -151,7 +151,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
options=[
|
||||
item.value.lower() for item in StatusType if item is not StatusType.UNKNOWN
|
||||
item.value.lower() for item in StatusType if item != StatusType.UNKNOWN
|
||||
],
|
||||
value_fn=(
|
||||
lambda data: (
|
||||
@@ -159,7 +159,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
if (
|
||||
data.tasks is not None
|
||||
and data.tasks.index_status is not None
|
||||
and data.tasks.index_status is not StatusType.UNKNOWN
|
||||
and data.tasks.index_status != StatusType.UNKNOWN
|
||||
)
|
||||
else None
|
||||
)
|
||||
@@ -172,7 +172,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
options=[
|
||||
item.value.lower() for item in StatusType if item is not StatusType.UNKNOWN
|
||||
item.value.lower() for item in StatusType if item != StatusType.UNKNOWN
|
||||
],
|
||||
value_fn=(
|
||||
lambda data: (
|
||||
@@ -180,7 +180,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
if (
|
||||
data.tasks is not None
|
||||
and data.tasks.classifier_status is not None
|
||||
and data.tasks.classifier_status is not StatusType.UNKNOWN
|
||||
and data.tasks.classifier_status != StatusType.UNKNOWN
|
||||
)
|
||||
else None
|
||||
)
|
||||
@@ -193,7 +193,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
options=[
|
||||
item.value.lower() for item in StatusType if item is not StatusType.UNKNOWN
|
||||
item.value.lower() for item in StatusType if item != StatusType.UNKNOWN
|
||||
],
|
||||
value_fn=(
|
||||
lambda data: (
|
||||
@@ -201,7 +201,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
if (
|
||||
data.tasks is not None
|
||||
and data.tasks.celery_status is not None
|
||||
and data.tasks.celery_status is not StatusType.UNKNOWN
|
||||
and data.tasks.celery_status != StatusType.UNKNOWN
|
||||
)
|
||||
else None
|
||||
)
|
||||
@@ -213,7 +213,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
options=[
|
||||
item.value.lower() for item in StatusType if item is not StatusType.UNKNOWN
|
||||
item.value.lower() for item in StatusType if item != StatusType.UNKNOWN
|
||||
],
|
||||
value_fn=(
|
||||
lambda data: (
|
||||
@@ -221,7 +221,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
if (
|
||||
data.tasks is not None
|
||||
and data.tasks.redis_status is not None
|
||||
and data.tasks.redis_status is not StatusType.UNKNOWN
|
||||
and data.tasks.redis_status != StatusType.UNKNOWN
|
||||
)
|
||||
else None
|
||||
)
|
||||
@@ -233,7 +233,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
options=[
|
||||
item.value.lower() for item in StatusType if item is not StatusType.UNKNOWN
|
||||
item.value.lower() for item in StatusType if item != StatusType.UNKNOWN
|
||||
],
|
||||
value_fn=(
|
||||
lambda data: (
|
||||
@@ -241,7 +241,7 @@ SENSOR_STATUS: tuple[PaperlessEntityDescription[Status], ...] = (
|
||||
if (
|
||||
data.tasks is not None
|
||||
and data.tasks.sanity_check_status is not None
|
||||
and data.tasks.sanity_check_status is not StatusType.UNKNOWN
|
||||
and data.tasks.sanity_check_status != StatusType.UNKNOWN
|
||||
)
|
||||
else None
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ async def get_api_client(hass: HomeAssistant, config_entry_id: str) -> PicnicAPI
|
||||
entry: PicnicConfigEntry | None = hass.config_entries.async_get_entry(
|
||||
config_entry_id
|
||||
)
|
||||
if entry is None or entry.state is not ConfigEntryState.LOADED:
|
||||
if entry is None or entry.state != ConfigEntryState.LOADED:
|
||||
raise ValueError(f"Config entry with id {config_entry_id} not found!")
|
||||
return entry.runtime_data.picnic_api_client
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ class PlaystationNetwork:
|
||||
for title in self.trophy_titles
|
||||
if game_title_info["titleName"]
|
||||
== normalize_title(title.title_name or "")
|
||||
and next(iter(title.title_platform)) is PlatformType.PS_VITA
|
||||
and next(iter(title.title_platform)) == PlatformType.PS_VITA
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
@@ -64,21 +64,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: PooldoseConfigEntry) ->
|
||||
try:
|
||||
client_status = await client.connect()
|
||||
except TimeoutError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=entry.domain,
|
||||
translation_key="connect_timeout",
|
||||
f"Timeout connecting to PoolDose device: {err}"
|
||||
) from err
|
||||
except (ConnectionError, OSError) as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=entry.domain,
|
||||
translation_key="connect_failed",
|
||||
f"Failed to connect to PoolDose device: {err}"
|
||||
) from err
|
||||
|
||||
if client_status != RequestStatus.SUCCESS:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=entry.domain,
|
||||
translation_key="client_init_failed",
|
||||
translation_placeholders={"status": str(client_status.value)},
|
||||
f"Failed to create PoolDose client while initialization: {client_status}"
|
||||
)
|
||||
|
||||
# Create coordinator and perform first refresh
|
||||
|
||||
@@ -43,17 +43,17 @@ class PooldoseConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Validate the host and return (serial_number, api_versions, errors)."""
|
||||
client = PooldoseClient(host, websession=async_get_clientsession(self.hass))
|
||||
client_status = await client.connect()
|
||||
if client_status is RequestStatus.HOST_UNREACHABLE:
|
||||
if client_status == RequestStatus.HOST_UNREACHABLE:
|
||||
return None, None, {"base": "cannot_connect"}
|
||||
if client_status is RequestStatus.PARAMS_FETCH_FAILED:
|
||||
if client_status == RequestStatus.PARAMS_FETCH_FAILED:
|
||||
return None, None, {"base": "params_fetch_failed"}
|
||||
if client_status is not RequestStatus.SUCCESS:
|
||||
if client_status != RequestStatus.SUCCESS:
|
||||
return None, None, {"base": "cannot_connect"}
|
||||
|
||||
api_status, api_versions = client.check_apiversion_supported()
|
||||
if api_status is RequestStatus.NO_DATA:
|
||||
if api_status == RequestStatus.NO_DATA:
|
||||
return None, None, {"base": "api_not_set"}
|
||||
if api_status is RequestStatus.API_VERSION_UNSUPPORTED:
|
||||
if api_status == RequestStatus.API_VERSION_UNSUPPORTED:
|
||||
return None, api_versions, {"base": "api_not_supported"}
|
||||
|
||||
device_info = client.device_info
|
||||
|
||||
@@ -49,28 +49,23 @@ class PooldoseCoordinator(DataUpdateCoordinator[StructuredValuesDict]):
|
||||
try:
|
||||
status, instant_values = await self.client.instant_values_structured()
|
||||
except TimeoutError as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(
|
||||
translation_domain=self.config_entry.domain,
|
||||
translation_key="update_timeout",
|
||||
f"Timeout fetching data from PoolDose device: {err}"
|
||||
) from err
|
||||
except (ConnectionError, OSError) as err:
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(
|
||||
translation_domain=self.config_entry.domain,
|
||||
translation_key="update_connect_failed",
|
||||
f"Failed to connect to PoolDose device while fetching data: {err}"
|
||||
) from err
|
||||
|
||||
if status != RequestStatus.SUCCESS:
|
||||
raise UpdateFailed(
|
||||
translation_domain=self.config_entry.domain,
|
||||
translation_key="api_status_error",
|
||||
translation_placeholders={"status": str(status.value)},
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed(f"API returned status: {status}")
|
||||
|
||||
if not instant_values:
|
||||
raise UpdateFailed(
|
||||
translation_domain=self.config_entry.domain,
|
||||
translation_key="no_data_received",
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-not-translated
|
||||
raise UpdateFailed("No data received from API")
|
||||
|
||||
_LOGGER.debug("Instant values structured: %s", instant_values)
|
||||
return instant_values
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user