mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
2024.4.0 (#114764)
This commit is contained in:
commit
b61397656c
30
.coveragerc
30
.coveragerc
@ -202,6 +202,7 @@ omit =
|
|||||||
homeassistant/components/control4/__init__.py
|
homeassistant/components/control4/__init__.py
|
||||||
homeassistant/components/control4/director_utils.py
|
homeassistant/components/control4/director_utils.py
|
||||||
homeassistant/components/control4/light.py
|
homeassistant/components/control4/light.py
|
||||||
|
homeassistant/components/control4/media_player.py
|
||||||
homeassistant/components/coolmaster/coordinator.py
|
homeassistant/components/coolmaster/coordinator.py
|
||||||
homeassistant/components/cppm_tracker/device_tracker.py
|
homeassistant/components/cppm_tracker/device_tracker.py
|
||||||
homeassistant/components/crownstone/__init__.py
|
homeassistant/components/crownstone/__init__.py
|
||||||
@ -250,7 +251,7 @@ omit =
|
|||||||
homeassistant/components/dormakaba_dkey/lock.py
|
homeassistant/components/dormakaba_dkey/lock.py
|
||||||
homeassistant/components/dormakaba_dkey/sensor.py
|
homeassistant/components/dormakaba_dkey/sensor.py
|
||||||
homeassistant/components/dovado/*
|
homeassistant/components/dovado/*
|
||||||
homeassistant/components/downloader/*
|
homeassistant/components/downloader/__init__.py
|
||||||
homeassistant/components/dsmr_reader/__init__.py
|
homeassistant/components/dsmr_reader/__init__.py
|
||||||
homeassistant/components/dsmr_reader/definitions.py
|
homeassistant/components/dsmr_reader/definitions.py
|
||||||
homeassistant/components/dsmr_reader/sensor.py
|
homeassistant/components/dsmr_reader/sensor.py
|
||||||
@ -461,6 +462,10 @@ omit =
|
|||||||
homeassistant/components/frontier_silicon/browse_media.py
|
homeassistant/components/frontier_silicon/browse_media.py
|
||||||
homeassistant/components/frontier_silicon/media_player.py
|
homeassistant/components/frontier_silicon/media_player.py
|
||||||
homeassistant/components/futurenow/light.py
|
homeassistant/components/futurenow/light.py
|
||||||
|
homeassistant/components/fyta/__init__.py
|
||||||
|
homeassistant/components/fyta/coordinator.py
|
||||||
|
homeassistant/components/fyta/entity.py
|
||||||
|
homeassistant/components/fyta/sensor.py
|
||||||
homeassistant/components/garadget/cover.py
|
homeassistant/components/garadget/cover.py
|
||||||
homeassistant/components/garages_amsterdam/__init__.py
|
homeassistant/components/garages_amsterdam/__init__.py
|
||||||
homeassistant/components/garages_amsterdam/binary_sensor.py
|
homeassistant/components/garages_amsterdam/binary_sensor.py
|
||||||
@ -545,7 +550,6 @@ omit =
|
|||||||
homeassistant/components/homematic/notify.py
|
homeassistant/components/homematic/notify.py
|
||||||
homeassistant/components/homematic/sensor.py
|
homeassistant/components/homematic/sensor.py
|
||||||
homeassistant/components/homematic/switch.py
|
homeassistant/components/homematic/switch.py
|
||||||
homeassistant/components/homeworks/*
|
|
||||||
homeassistant/components/horizon/media_player.py
|
homeassistant/components/horizon/media_player.py
|
||||||
homeassistant/components/hp_ilo/sensor.py
|
homeassistant/components/hp_ilo/sensor.py
|
||||||
homeassistant/components/huawei_lte/__init__.py
|
homeassistant/components/huawei_lte/__init__.py
|
||||||
@ -744,7 +748,6 @@ omit =
|
|||||||
homeassistant/components/lyric/climate.py
|
homeassistant/components/lyric/climate.py
|
||||||
homeassistant/components/lyric/sensor.py
|
homeassistant/components/lyric/sensor.py
|
||||||
homeassistant/components/mailgun/notify.py
|
homeassistant/components/mailgun/notify.py
|
||||||
homeassistant/components/map/*
|
|
||||||
homeassistant/components/mastodon/notify.py
|
homeassistant/components/mastodon/notify.py
|
||||||
homeassistant/components/matrix/__init__.py
|
homeassistant/components/matrix/__init__.py
|
||||||
homeassistant/components/matrix/notify.py
|
homeassistant/components/matrix/notify.py
|
||||||
@ -773,9 +776,11 @@ omit =
|
|||||||
homeassistant/components/microbees/__init__.py
|
homeassistant/components/microbees/__init__.py
|
||||||
homeassistant/components/microbees/api.py
|
homeassistant/components/microbees/api.py
|
||||||
homeassistant/components/microbees/application_credentials.py
|
homeassistant/components/microbees/application_credentials.py
|
||||||
|
homeassistant/components/microbees/binary_sensor.py
|
||||||
homeassistant/components/microbees/button.py
|
homeassistant/components/microbees/button.py
|
||||||
homeassistant/components/microbees/const.py
|
homeassistant/components/microbees/const.py
|
||||||
homeassistant/components/microbees/coordinator.py
|
homeassistant/components/microbees/coordinator.py
|
||||||
|
homeassistant/components/microbees/cover.py
|
||||||
homeassistant/components/microbees/entity.py
|
homeassistant/components/microbees/entity.py
|
||||||
homeassistant/components/microbees/light.py
|
homeassistant/components/microbees/light.py
|
||||||
homeassistant/components/microbees/sensor.py
|
homeassistant/components/microbees/sensor.py
|
||||||
@ -801,6 +806,11 @@ omit =
|
|||||||
homeassistant/components/motion_blinds/cover.py
|
homeassistant/components/motion_blinds/cover.py
|
||||||
homeassistant/components/motion_blinds/entity.py
|
homeassistant/components/motion_blinds/entity.py
|
||||||
homeassistant/components/motion_blinds/sensor.py
|
homeassistant/components/motion_blinds/sensor.py
|
||||||
|
homeassistant/components/motionblinds_ble/__init__.py
|
||||||
|
homeassistant/components/motionblinds_ble/button.py
|
||||||
|
homeassistant/components/motionblinds_ble/cover.py
|
||||||
|
homeassistant/components/motionblinds_ble/entity.py
|
||||||
|
homeassistant/components/motionblinds_ble/select.py
|
||||||
homeassistant/components/motionmount/__init__.py
|
homeassistant/components/motionmount/__init__.py
|
||||||
homeassistant/components/motionmount/binary_sensor.py
|
homeassistant/components/motionmount/binary_sensor.py
|
||||||
homeassistant/components/motionmount/entity.py
|
homeassistant/components/motionmount/entity.py
|
||||||
@ -888,6 +898,7 @@ omit =
|
|||||||
homeassistant/components/notify_events/notify.py
|
homeassistant/components/notify_events/notify.py
|
||||||
homeassistant/components/notion/__init__.py
|
homeassistant/components/notion/__init__.py
|
||||||
homeassistant/components/notion/binary_sensor.py
|
homeassistant/components/notion/binary_sensor.py
|
||||||
|
homeassistant/components/notion/coordinator.py
|
||||||
homeassistant/components/notion/sensor.py
|
homeassistant/components/notion/sensor.py
|
||||||
homeassistant/components/notion/util.py
|
homeassistant/components/notion/util.py
|
||||||
homeassistant/components/nsw_fuel_station/sensor.py
|
homeassistant/components/nsw_fuel_station/sensor.py
|
||||||
@ -923,7 +934,6 @@ omit =
|
|||||||
homeassistant/components/onvif/sensor.py
|
homeassistant/components/onvif/sensor.py
|
||||||
homeassistant/components/onvif/util.py
|
homeassistant/components/onvif/util.py
|
||||||
homeassistant/components/open_meteo/weather.py
|
homeassistant/components/open_meteo/weather.py
|
||||||
homeassistant/components/opencv/*
|
|
||||||
homeassistant/components/openevse/sensor.py
|
homeassistant/components/openevse/sensor.py
|
||||||
homeassistant/components/openexchangerates/__init__.py
|
homeassistant/components/openexchangerates/__init__.py
|
||||||
homeassistant/components/openexchangerates/coordinator.py
|
homeassistant/components/openexchangerates/coordinator.py
|
||||||
@ -946,7 +956,9 @@ omit =
|
|||||||
homeassistant/components/openuv/binary_sensor.py
|
homeassistant/components/openuv/binary_sensor.py
|
||||||
homeassistant/components/openuv/coordinator.py
|
homeassistant/components/openuv/coordinator.py
|
||||||
homeassistant/components/openuv/sensor.py
|
homeassistant/components/openuv/sensor.py
|
||||||
|
homeassistant/components/openweathermap/__init__.py
|
||||||
homeassistant/components/openweathermap/sensor.py
|
homeassistant/components/openweathermap/sensor.py
|
||||||
|
homeassistant/components/openweathermap/weather.py
|
||||||
homeassistant/components/openweathermap/weather_update_coordinator.py
|
homeassistant/components/openweathermap/weather_update_coordinator.py
|
||||||
homeassistant/components/opnsense/__init__.py
|
homeassistant/components/opnsense/__init__.py
|
||||||
homeassistant/components/opower/__init__.py
|
homeassistant/components/opower/__init__.py
|
||||||
@ -988,7 +1000,9 @@ omit =
|
|||||||
homeassistant/components/pandora/media_player.py
|
homeassistant/components/pandora/media_player.py
|
||||||
homeassistant/components/pencom/switch.py
|
homeassistant/components/pencom/switch.py
|
||||||
homeassistant/components/permobil/__init__.py
|
homeassistant/components/permobil/__init__.py
|
||||||
|
homeassistant/components/permobil/binary_sensor.py
|
||||||
homeassistant/components/permobil/coordinator.py
|
homeassistant/components/permobil/coordinator.py
|
||||||
|
homeassistant/components/permobil/entity.py
|
||||||
homeassistant/components/permobil/sensor.py
|
homeassistant/components/permobil/sensor.py
|
||||||
homeassistant/components/philips_js/__init__.py
|
homeassistant/components/philips_js/__init__.py
|
||||||
homeassistant/components/philips_js/light.py
|
homeassistant/components/philips_js/light.py
|
||||||
@ -1055,6 +1069,7 @@ omit =
|
|||||||
homeassistant/components/rabbitair/fan.py
|
homeassistant/components/rabbitair/fan.py
|
||||||
homeassistant/components/rachio/__init__.py
|
homeassistant/components/rachio/__init__.py
|
||||||
homeassistant/components/rachio/binary_sensor.py
|
homeassistant/components/rachio/binary_sensor.py
|
||||||
|
homeassistant/components/rachio/coordinator.py
|
||||||
homeassistant/components/rachio/device.py
|
homeassistant/components/rachio/device.py
|
||||||
homeassistant/components/rachio/entity.py
|
homeassistant/components/rachio/entity.py
|
||||||
homeassistant/components/rachio/switch.py
|
homeassistant/components/rachio/switch.py
|
||||||
@ -1127,6 +1142,7 @@ omit =
|
|||||||
homeassistant/components/rocketchat/notify.py
|
homeassistant/components/rocketchat/notify.py
|
||||||
homeassistant/components/romy/__init__.py
|
homeassistant/components/romy/__init__.py
|
||||||
homeassistant/components/romy/coordinator.py
|
homeassistant/components/romy/coordinator.py
|
||||||
|
homeassistant/components/romy/entity.py
|
||||||
homeassistant/components/romy/vacuum.py
|
homeassistant/components/romy/vacuum.py
|
||||||
homeassistant/components/roomba/__init__.py
|
homeassistant/components/roomba/__init__.py
|
||||||
homeassistant/components/roomba/binary_sensor.py
|
homeassistant/components/roomba/binary_sensor.py
|
||||||
@ -1141,7 +1157,6 @@ omit =
|
|||||||
homeassistant/components/roon/media_player.py
|
homeassistant/components/roon/media_player.py
|
||||||
homeassistant/components/roon/server.py
|
homeassistant/components/roon/server.py
|
||||||
homeassistant/components/route53/*
|
homeassistant/components/route53/*
|
||||||
homeassistant/components/rova/sensor.py
|
|
||||||
homeassistant/components/rpi_camera/*
|
homeassistant/components/rpi_camera/*
|
||||||
homeassistant/components/rtorrent/sensor.py
|
homeassistant/components/rtorrent/sensor.py
|
||||||
homeassistant/components/ruuvi_gateway/__init__.py
|
homeassistant/components/ruuvi_gateway/__init__.py
|
||||||
@ -1179,7 +1194,6 @@ omit =
|
|||||||
homeassistant/components/serial_pm/sensor.py
|
homeassistant/components/serial_pm/sensor.py
|
||||||
homeassistant/components/sesame/lock.py
|
homeassistant/components/sesame/lock.py
|
||||||
homeassistant/components/seven_segments/image_processing.py
|
homeassistant/components/seven_segments/image_processing.py
|
||||||
homeassistant/components/seventeentrack/sensor.py
|
|
||||||
homeassistant/components/shodan/sensor.py
|
homeassistant/components/shodan/sensor.py
|
||||||
homeassistant/components/sia/__init__.py
|
homeassistant/components/sia/__init__.py
|
||||||
homeassistant/components/sia/alarm_control_panel.py
|
homeassistant/components/sia/alarm_control_panel.py
|
||||||
@ -1281,6 +1295,7 @@ omit =
|
|||||||
homeassistant/components/starlink/device_tracker.py
|
homeassistant/components/starlink/device_tracker.py
|
||||||
homeassistant/components/starlink/sensor.py
|
homeassistant/components/starlink/sensor.py
|
||||||
homeassistant/components/starlink/switch.py
|
homeassistant/components/starlink/switch.py
|
||||||
|
homeassistant/components/starlink/time.py
|
||||||
homeassistant/components/starline/__init__.py
|
homeassistant/components/starline/__init__.py
|
||||||
homeassistant/components/starline/account.py
|
homeassistant/components/starline/account.py
|
||||||
homeassistant/components/starline/binary_sensor.py
|
homeassistant/components/starline/binary_sensor.py
|
||||||
@ -1425,6 +1440,7 @@ omit =
|
|||||||
homeassistant/components/tolo/number.py
|
homeassistant/components/tolo/number.py
|
||||||
homeassistant/components/tolo/select.py
|
homeassistant/components/tolo/select.py
|
||||||
homeassistant/components/tolo/sensor.py
|
homeassistant/components/tolo/sensor.py
|
||||||
|
homeassistant/components/tolo/switch.py
|
||||||
homeassistant/components/toon/__init__.py
|
homeassistant/components/toon/__init__.py
|
||||||
homeassistant/components/toon/binary_sensor.py
|
homeassistant/components/toon/binary_sensor.py
|
||||||
homeassistant/components/toon/climate.py
|
homeassistant/components/toon/climate.py
|
||||||
@ -1594,7 +1610,6 @@ omit =
|
|||||||
homeassistant/components/weatherflow_cloud/const.py
|
homeassistant/components/weatherflow_cloud/const.py
|
||||||
homeassistant/components/weatherflow_cloud/coordinator.py
|
homeassistant/components/weatherflow_cloud/coordinator.py
|
||||||
homeassistant/components/weatherflow_cloud/weather.py
|
homeassistant/components/weatherflow_cloud/weather.py
|
||||||
homeassistant/components/webmin/sensor.py
|
|
||||||
homeassistant/components/wiffi/__init__.py
|
homeassistant/components/wiffi/__init__.py
|
||||||
homeassistant/components/wiffi/binary_sensor.py
|
homeassistant/components/wiffi/binary_sensor.py
|
||||||
homeassistant/components/wiffi/sensor.py
|
homeassistant/components/wiffi/sensor.py
|
||||||
@ -1677,6 +1692,7 @@ omit =
|
|||||||
homeassistant/components/yolink/services.py
|
homeassistant/components/yolink/services.py
|
||||||
homeassistant/components/yolink/siren.py
|
homeassistant/components/yolink/siren.py
|
||||||
homeassistant/components/yolink/switch.py
|
homeassistant/components/yolink/switch.py
|
||||||
|
homeassistant/components/yolink/valve.py
|
||||||
homeassistant/components/youless/__init__.py
|
homeassistant/components/youless/__init__.py
|
||||||
homeassistant/components/youless/sensor.py
|
homeassistant/components/youless/sensor.py
|
||||||
homeassistant/components/zabbix/*
|
homeassistant/components/zabbix/*
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
],
|
],
|
||||||
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json
|
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"python.experiments.optOutFrom": ["pythonTestAdapter"],
|
||||||
"python.pythonPath": "/usr/local/bin/python",
|
"python.pythonPath": "/usr/local/bin/python",
|
||||||
"python.testing.pytestArgs": ["--no-cov"],
|
"python.testing.pytestArgs": ["--no-cov"],
|
||||||
"editor.formatOnPaste": false,
|
"editor.formatOnPaste": false,
|
||||||
|
14
.git-blame-ignore-revs
Normal file
14
.git-blame-ignore-revs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Black
|
||||||
|
4de97abc3aa83188666336ce0a015a5bab75bc8f
|
||||||
|
|
||||||
|
# Switch formatting from black to ruff-format (#102893)
|
||||||
|
706add4a57120a93d7b7fe40e722b00d634c76c2
|
||||||
|
|
||||||
|
# Prettify json (component test fixtures) (#68892)
|
||||||
|
053c4428a933c3c04c22642f93c93fccba3e8bfd
|
||||||
|
|
||||||
|
# Prettify json (tests) (#68888)
|
||||||
|
496d90bf00429d9d924caeb0155edc0bf54e86b9
|
||||||
|
|
||||||
|
# Bump ruff to 0.3.4 (#112690)
|
||||||
|
6bb4e7d62c60389608acf4a7d7dacd8f029307dd
|
220
.github/workflows/builder.yml
vendored
220
.github/workflows/builder.yml
vendored
@ -12,6 +12,8 @@ env:
|
|||||||
BUILD_TYPE: core
|
BUILD_TYPE: core
|
||||||
DEFAULT_PYTHON: "3.12"
|
DEFAULT_PYTHON: "3.12"
|
||||||
PIP_TIMEOUT: 60
|
PIP_TIMEOUT: 60
|
||||||
|
UV_HTTP_TIMEOUT: 60
|
||||||
|
UV_SYSTEM_PYTHON: "true"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
init:
|
init:
|
||||||
@ -25,12 +27,12 @@ jobs:
|
|||||||
publish: ${{ steps.version.outputs.publish }}
|
publish: ${{ steps.version.outputs.publish }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
@ -49,41 +51,29 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ignore-dev: true
|
ignore-dev: true
|
||||||
|
|
||||||
build_python:
|
- name: Fail if translations files are checked in
|
||||||
name: Build PyPi package
|
run: |
|
||||||
environment: ${{ needs.init.outputs.channel }}
|
if [ -n "$(find homeassistant/components/*/translations -type f)" ]; then
|
||||||
needs: ["init", "build_base"]
|
echo "Translations files are checked in, please remove the following files:"
|
||||||
runs-on: ubuntu-latest
|
find homeassistant/components/*/translations -type f
|
||||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
exit 1
|
||||||
steps:
|
fi
|
||||||
- name: Checkout the repository
|
|
||||||
uses: actions/checkout@v4.1.1
|
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
||||||
uses: actions/setup-python@v5.0.0
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
||||||
|
|
||||||
- name: Download Translations
|
- name: Download Translations
|
||||||
run: python3 -m script.translations download
|
run: python3 -m script.translations download
|
||||||
env:
|
env:
|
||||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||||
|
|
||||||
- name: Build package
|
- name: Archive translations
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
|
||||||
# Remove dist, build, and homeassistant.egg-info
|
|
||||||
# when build locally for testing!
|
|
||||||
pip install twine build
|
|
||||||
python -m build
|
|
||||||
|
|
||||||
- name: Upload package
|
- name: Upload translations
|
||||||
shell: bash
|
uses: actions/upload-artifact@v4.3.1
|
||||||
run: |
|
with:
|
||||||
export TWINE_USERNAME="__token__"
|
name: translations
|
||||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
path: translations.tar.gz
|
||||||
|
if-no-files-found: error
|
||||||
twine upload dist/* --skip-existing
|
|
||||||
|
|
||||||
build_base:
|
build_base:
|
||||||
name: Build ${{ matrix.arch }} base core image
|
name: Build ${{ matrix.arch }} base core image
|
||||||
@ -95,15 +85,16 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
id-token: write
|
id-token: write
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Download nightly wheels of frontend
|
- name: Download nightly wheels of frontend
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
uses: dawidd6/action-download-artifact@v3.1.2
|
uses: dawidd6/action-download-artifact@v3.1.4
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
repo: home-assistant/frontend
|
repo: home-assistant/frontend
|
||||||
@ -114,7 +105,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download nightly wheels of intents
|
- name: Download nightly wheels of intents
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
uses: dawidd6/action-download-artifact@v3.1.2
|
uses: dawidd6/action-download-artifact@v3.1.4
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
repo: home-assistant/intents-package
|
repo: home-assistant/intents-package
|
||||||
@ -125,17 +116,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
- name: Adjust nightly version
|
- name: Adjust nightly version
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
UV_PRERELEASE: allow
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install packaging tomli
|
python3 -m pip install "$(grep '^uv' < requirements_test.txt)"
|
||||||
python3 -m pip install .
|
uv pip install packaging tomli
|
||||||
version="$(python3 script/version_bump.py nightly)"
|
uv pip install .
|
||||||
|
python3 script/version_bump.py nightly --set-nightly-version "${{ needs.init.outputs.version }}"
|
||||||
|
|
||||||
if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then
|
if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then
|
||||||
echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}"
|
echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}"
|
||||||
@ -147,7 +141,7 @@ jobs:
|
|||||||
sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \
|
sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \
|
||||||
homeassistant/package_constraints.txt
|
homeassistant/package_constraints.txt
|
||||||
|
|
||||||
python -m script.gen_requirements_all
|
sed -i "s|home-assistant-frontend==.*||" requirements_all.txt
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$(ls home_assistant_intents*.whl)" =~ ^home_assistant_intents-(.*)-py3-none-any.whl$ ]]; then
|
if [[ "$(ls home_assistant_intents*.whl)" =~ ^home_assistant_intents-(.*)-py3-none-any.whl$ ]]; then
|
||||||
@ -165,7 +159,7 @@ jobs:
|
|||||||
sed -i "s|home-assistant-intents==.*|home-assistant-intents==${BASH_REMATCH[1]}|" \
|
sed -i "s|home-assistant-intents==.*|home-assistant-intents==${BASH_REMATCH[1]}|" \
|
||||||
homeassistant/package_constraints.txt
|
homeassistant/package_constraints.txt
|
||||||
|
|
||||||
python -m script.gen_requirements_all
|
sed -i "s|home-assistant-intents==.*||" requirements_all.txt
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Adjustments for armhf
|
- name: Adjustments for armhf
|
||||||
@ -189,10 +183,15 @@ jobs:
|
|||||||
# are not available.
|
# are not available.
|
||||||
sed -i "s|aiohttp-zlib-ng|aiohttp-zlib-ng\[isal\]|g" requirements_all.txt
|
sed -i "s|aiohttp-zlib-ng|aiohttp-zlib-ng\[isal\]|g" requirements_all.txt
|
||||||
|
|
||||||
- name: Download Translations
|
- name: Download translations
|
||||||
run: python3 -m script.translations download
|
uses: actions/download-artifact@v4.1.4
|
||||||
env:
|
with:
|
||||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
name: translations
|
||||||
|
|
||||||
|
- name: Extract translations
|
||||||
|
run: |
|
||||||
|
tar xvf translations.tar.gz
|
||||||
|
rm translations.tar.gz
|
||||||
|
|
||||||
- name: Write meta info file
|
- name: Write meta info file
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -200,7 +199,7 @@ jobs:
|
|||||||
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3.0.0
|
uses: docker/login-action@v3.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@ -216,17 +215,6 @@ jobs:
|
|||||||
--target /data \
|
--target /data \
|
||||||
--generic ${{ needs.init.outputs.version }}
|
--generic ${{ needs.init.outputs.version }}
|
||||||
|
|
||||||
- name: Archive translations
|
|
||||||
shell: bash
|
|
||||||
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
|
|
||||||
|
|
||||||
- name: Upload translations
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: translations
|
|
||||||
path: translations.tar.gz
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
build_machine:
|
build_machine:
|
||||||
name: Build ${{ matrix.machine }} machine core image
|
name: Build ${{ matrix.machine }} machine core image
|
||||||
if: github.repository_owner == 'home-assistant'
|
if: github.repository_owner == 'home-assistant'
|
||||||
@ -263,7 +251,7 @@ jobs:
|
|||||||
- green
|
- green
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Set build additional args
|
- name: Set build additional args
|
||||||
run: |
|
run: |
|
||||||
@ -277,7 +265,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3.0.0
|
uses: docker/login-action@v3.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@ -300,7 +288,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Initialize git
|
- name: Initialize git
|
||||||
uses: home-assistant/actions/helpers/git-init@master
|
uses: home-assistant/actions/helpers/git-init@master
|
||||||
@ -336,9 +324,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@v3.4.0
|
uses: sigstore/cosign-installer@v3.4.0
|
||||||
@ -346,13 +337,15 @@ jobs:
|
|||||||
cosign-release: "v2.2.3"
|
cosign-release: "v2.2.3"
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3.0.0
|
if: matrix.registry == 'docker.io/homeassistant'
|
||||||
|
uses: docker/login-action@v3.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3.0.0
|
if: matrix.registry == 'ghcr.io/home-assistant'
|
||||||
|
uses: docker/login-action@v3.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@ -366,41 +359,37 @@ jobs:
|
|||||||
function create_manifest() {
|
function create_manifest() {
|
||||||
local tag_l=${1}
|
local tag_l=${1}
|
||||||
local tag_r=${2}
|
local tag_r=${2}
|
||||||
|
local registry=${{ matrix.registry }}
|
||||||
|
|
||||||
for registry in "ghcr.io/home-assistant" "docker.io/homeassistant"
|
docker manifest create "${registry}/home-assistant:${tag_l}" \
|
||||||
do
|
"${registry}/amd64-homeassistant:${tag_r}" \
|
||||||
|
"${registry}/i386-homeassistant:${tag_r}" \
|
||||||
|
"${registry}/armhf-homeassistant:${tag_r}" \
|
||||||
|
"${registry}/armv7-homeassistant:${tag_r}" \
|
||||||
|
"${registry}/aarch64-homeassistant:${tag_r}"
|
||||||
|
|
||||||
docker manifest create "${registry}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${registry}/amd64-homeassistant:${tag_r}" \
|
"${registry}/amd64-homeassistant:${tag_r}" \
|
||||||
"${registry}/i386-homeassistant:${tag_r}" \
|
--os linux --arch amd64
|
||||||
"${registry}/armhf-homeassistant:${tag_r}" \
|
|
||||||
"${registry}/armv7-homeassistant:${tag_r}" \
|
|
||||||
"${registry}/aarch64-homeassistant:${tag_r}"
|
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${registry}/amd64-homeassistant:${tag_r}" \
|
"${registry}/i386-homeassistant:${tag_r}" \
|
||||||
--os linux --arch amd64
|
--os linux --arch 386
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${registry}/i386-homeassistant:${tag_r}" \
|
"${registry}/armhf-homeassistant:${tag_r}" \
|
||||||
--os linux --arch 386
|
--os linux --arch arm --variant=v6
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${registry}/armhf-homeassistant:${tag_r}" \
|
"${registry}/armv7-homeassistant:${tag_r}" \
|
||||||
--os linux --arch arm --variant=v6
|
--os linux --arch arm --variant=v7
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${registry}/armv7-homeassistant:${tag_r}" \
|
"${registry}/aarch64-homeassistant:${tag_r}" \
|
||||||
--os linux --arch arm --variant=v7
|
--os linux --arch arm64 --variant=v8
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
docker manifest push --purge "${registry}/home-assistant:${tag_l}"
|
||||||
"${registry}/aarch64-homeassistant:${tag_r}" \
|
cosign sign --yes "${registry}/home-assistant:${tag_l}"
|
||||||
--os linux --arch arm64 --variant=v8
|
|
||||||
|
|
||||||
docker manifest push --purge "${registry}/home-assistant:${tag_l}"
|
|
||||||
cosign sign --yes "${registry}/home-assistant:${tag_l}"
|
|
||||||
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate_image() {
|
function validate_image() {
|
||||||
@ -433,12 +422,14 @@ jobs:
|
|||||||
validate_image "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
validate_image "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
|
||||||
# Upload images to dockerhub
|
if [[ "${{ matrix.registry }}" == "docker.io/homeassistant" ]]; then
|
||||||
push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}"
|
# Upload images to dockerhub
|
||||||
push_dockerhub "i386-homeassistant" "${{ needs.init.outputs.version }}"
|
push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
push_dockerhub "armhf-homeassistant" "${{ needs.init.outputs.version }}"
|
push_dockerhub "i386-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
push_dockerhub "armv7-homeassistant" "${{ needs.init.outputs.version }}"
|
push_dockerhub "armhf-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}"
|
push_dockerhub "armv7-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
|
push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Create version tag
|
# Create version tag
|
||||||
create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}"
|
create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}"
|
||||||
@ -459,3 +450,44 @@ jobs:
|
|||||||
v="${{ needs.init.outputs.version }}"
|
v="${{ needs.init.outputs.version }}"
|
||||||
create_manifest "${v%.*}" "${{ needs.init.outputs.version }}"
|
create_manifest "${v%.*}" "${{ needs.init.outputs.version }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
build_python:
|
||||||
|
name: Build PyPi package
|
||||||
|
environment: ${{ needs.init.outputs.channel }}
|
||||||
|
needs: ["init", "build_base"]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||||
|
steps:
|
||||||
|
- name: Checkout the repository
|
||||||
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
|
uses: actions/setup-python@v5.1.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
|
- name: Download translations
|
||||||
|
uses: actions/download-artifact@v4.1.4
|
||||||
|
with:
|
||||||
|
name: translations
|
||||||
|
|
||||||
|
- name: Extract translations
|
||||||
|
run: |
|
||||||
|
tar xvf translations.tar.gz
|
||||||
|
rm translations.tar.gz
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Remove dist, build, and homeassistant.egg-info
|
||||||
|
# when build locally for testing!
|
||||||
|
pip install twine build
|
||||||
|
python -m build
|
||||||
|
|
||||||
|
- name: Upload package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export TWINE_USERNAME="__token__"
|
||||||
|
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
||||||
|
|
||||||
|
twine upload dist/* --skip-existing
|
||||||
|
178
.github/workflows/ci.yaml
vendored
178
.github/workflows/ci.yaml
vendored
@ -34,11 +34,11 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
CACHE_VERSION: 5
|
CACHE_VERSION: 5
|
||||||
PIP_CACHE_VERSION: 4
|
UV_CACHE_VERSION: 1
|
||||||
MYPY_CACHE_VERSION: 7
|
MYPY_CACHE_VERSION: 8
|
||||||
HA_SHORT_VERSION: "2024.3"
|
HA_SHORT_VERSION: "2024.4"
|
||||||
DEFAULT_PYTHON: "3.11"
|
DEFAULT_PYTHON: "3.12"
|
||||||
ALL_PYTHON_VERSIONS: "['3.11', '3.12']"
|
ALL_PYTHON_VERSIONS: "['3.12']"
|
||||||
# 10.3 is the oldest supported version
|
# 10.3 is the oldest supported version
|
||||||
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
|
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
|
||||||
# 10.6 is the current long-term-support
|
# 10.6 is the current long-term-support
|
||||||
@ -56,7 +56,7 @@ env:
|
|||||||
# - 15.2 is the latest (as of 9 Feb 2023)
|
# - 15.2 is the latest (as of 9 Feb 2023)
|
||||||
POSTGRESQL_VERSIONS: "['postgres:12.14','postgres:15.2']"
|
POSTGRESQL_VERSIONS: "['postgres:12.14','postgres:15.2']"
|
||||||
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
||||||
PIP_CACHE: /tmp/pip-cache
|
UV_CACHE_DIR: /tmp/uv-cache
|
||||||
SQLALCHEMY_WARN_20: 1
|
SQLALCHEMY_WARN_20: 1
|
||||||
PYTHONASYNCIODEBUG: 1
|
PYTHONASYNCIODEBUG: 1
|
||||||
HASS_CI: 1
|
HASS_CI: 1
|
||||||
@ -89,7 +89,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Generate partial Python venv restore key
|
- name: Generate partial Python venv restore key
|
||||||
id: generate_python_cache_key
|
id: generate_python_cache_key
|
||||||
run: >-
|
run: >-
|
||||||
@ -103,7 +103,7 @@ jobs:
|
|||||||
echo "key=pre-commit-${{ env.CACHE_VERSION }}-${{
|
echo "key=pre-commit-${{ env.CACHE_VERSION }}-${{
|
||||||
hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||||
- name: Filter for core changes
|
- name: Filter for core changes
|
||||||
uses: dorny/paths-filter@v3.0.1
|
uses: dorny/paths-filter@v3.0.2
|
||||||
id: core
|
id: core
|
||||||
with:
|
with:
|
||||||
filters: .core_files.yaml
|
filters: .core_files.yaml
|
||||||
@ -118,7 +118,7 @@ jobs:
|
|||||||
echo "Result:"
|
echo "Result:"
|
||||||
cat .integration_paths.yaml
|
cat .integration_paths.yaml
|
||||||
- name: Filter for integration changes
|
- name: Filter for integration changes
|
||||||
uses: dorny/paths-filter@v3.0.1
|
uses: dorny/paths-filter@v3.0.2
|
||||||
id: integrations
|
id: integrations
|
||||||
with:
|
with:
|
||||||
filters: .integration_paths.yaml
|
filters: .integration_paths.yaml
|
||||||
@ -222,16 +222,16 @@ jobs:
|
|||||||
- info
|
- info
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.0.0
|
uses: actions/cache@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: >-
|
key: >-
|
||||||
@ -243,10 +243,11 @@ jobs:
|
|||||||
python -m venv venv
|
python -m venv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
pip install "$(cat requirements_test.txt | grep pre-commit)"
|
pip install "$(grep '^uv' < requirements_test.txt)"
|
||||||
|
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache@v4.0.0
|
uses: actions/cache@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
lookup-only: true
|
lookup-only: true
|
||||||
@ -267,16 +268,16 @@ jobs:
|
|||||||
- pre-commit
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -285,7 +286,7 @@ jobs:
|
|||||||
needs.info.outputs.pre-commit_cache_key }}
|
needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -307,16 +308,16 @@ jobs:
|
|||||||
- pre-commit
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -325,7 +326,7 @@ jobs:
|
|||||||
needs.info.outputs.pre-commit_cache_key }}
|
needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -346,16 +347,16 @@ jobs:
|
|||||||
- pre-commit
|
- pre-commit
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
id: python
|
id: python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -364,7 +365,7 @@ jobs:
|
|||||||
needs.info.outputs.pre-commit_cache_key }}
|
needs.info.outputs.pre-commit_cache_key }}
|
||||||
- name: Restore pre-commit environment from cache
|
- name: Restore pre-commit environment from cache
|
||||||
id: cache-precommit
|
id: cache-precommit
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -440,37 +441,37 @@ jobs:
|
|||||||
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Generate partial pip restore key
|
- name: Generate partial uv restore key
|
||||||
id: generate-pip-key
|
id: generate-uv-key
|
||||||
run: >-
|
run: >-
|
||||||
echo "key=pip-${{ env.PIP_CACHE_VERSION }}-${{
|
echo "key=uv-${{ env.UV_CACHE_VERSION }}-${{
|
||||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.0.0
|
uses: actions/cache@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
lookup-only: true
|
lookup-only: true
|
||||||
key: >-
|
key: >-
|
||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
needs.info.outputs.python_cache_key }}
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Restore pip wheel cache
|
- name: Restore uv wheel cache
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
uses: actions/cache@v4.0.0
|
uses: actions/cache@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ${{ env.PIP_CACHE }}
|
path: ${{ env.UV_CACHE_DIR }}
|
||||||
key: >-
|
key: >-
|
||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
steps.generate-pip-key.outputs.key }}
|
steps.generate-uv-key.outputs.key }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-uv-${{ env.UV_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
|
||||||
- name: Install additional OS dependencies
|
- name: Install additional OS dependencies
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
@ -492,10 +493,11 @@ jobs:
|
|||||||
python -m venv venv
|
python -m venv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
python --version
|
python --version
|
||||||
PIP_CACHE_DIR=$PIP_CACHE pip install -U "pip>=21.3.1" setuptools wheel
|
pip install "$(grep '^uv' < requirements_test.txt)"
|
||||||
PIP_CACHE_DIR=$PIP_CACHE pip install -r requirements_all.txt
|
uv pip install -U "pip>=21.3.1" setuptools wheel
|
||||||
PIP_CACHE_DIR=$PIP_CACHE pip install -r requirements_test.txt
|
uv pip install -r requirements_all.txt
|
||||||
pip install -e . --config-settings editable_mode=compat
|
uv pip install -r requirements_test.txt
|
||||||
|
uv pip install -e . --config-settings editable_mode=compat
|
||||||
|
|
||||||
hassfest:
|
hassfest:
|
||||||
name: Check hassfest
|
name: Check hassfest
|
||||||
@ -508,16 +510,16 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -540,16 +542,16 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -573,16 +575,16 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -617,10 +619,10 @@ jobs:
|
|||||||
- base
|
- base
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@ -633,7 +635,7 @@ jobs:
|
|||||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -641,7 +643,7 @@ jobs:
|
|||||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||||
needs.info.outputs.python_cache_key }}
|
needs.info.outputs.python_cache_key }}
|
||||||
- name: Restore mypy cache
|
- name: Restore mypy cache
|
||||||
uses: actions/cache@v4.0.0
|
uses: actions/cache@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: .mypy_cache
|
path: .mypy_cache
|
||||||
key: >-
|
key: >-
|
||||||
@ -699,16 +701,16 @@ jobs:
|
|||||||
bluez \
|
bluez \
|
||||||
ffmpeg
|
ffmpeg
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -717,13 +719,6 @@ jobs:
|
|||||||
- name: Register Python problem matcher
|
- name: Register Python problem matcher
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||||
- name: Install Pytest Annotation plugin
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
# Ideally this should be part of our dependencies
|
|
||||||
# However this plugin is fairly new and doesn't run correctly
|
|
||||||
# on a non-GitHub environment.
|
|
||||||
pip install pytest-github-actions-annotate-failures==0.1.3
|
|
||||||
- name: Register pytest slow test problem matcher
|
- name: Register pytest slow test problem matcher
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||||
@ -797,10 +792,11 @@ jobs:
|
|||||||
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
||||||
- name: Upload pytest output
|
- name: Upload pytest output
|
||||||
if: success() || failure() && (steps.pytest-full.conclusion == 'failure' || steps.pytest-partial.conclusion == 'failure')
|
if: success() || failure() && (steps.pytest-full.conclusion == 'failure' || steps.pytest-partial.conclusion == 'failure')
|
||||||
uses: actions/upload-artifact@v3.1.2
|
uses: actions/upload-artifact@v4.3.1
|
||||||
with:
|
with:
|
||||||
name: pytest-${{ github.run_number }}
|
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
|
||||||
path: pytest-*.txt
|
path: pytest-*.txt
|
||||||
|
overwrite: true
|
||||||
- name: Upload coverage artifact
|
- name: Upload coverage artifact
|
||||||
if: needs.info.outputs.skip_coverage != 'true'
|
if: needs.info.outputs.skip_coverage != 'true'
|
||||||
uses: actions/upload-artifact@v4.3.1
|
uses: actions/upload-artifact@v4.3.1
|
||||||
@ -852,16 +848,16 @@ jobs:
|
|||||||
ffmpeg \
|
ffmpeg \
|
||||||
libmariadb-dev-compat
|
libmariadb-dev-compat
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -870,20 +866,13 @@ jobs:
|
|||||||
- name: Register Python problem matcher
|
- name: Register Python problem matcher
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||||
- name: Install Pytest Annotation plugin
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
# Ideally this should be part of our dependencies
|
|
||||||
# However this plugin is fairly new and doesn't run correctly
|
|
||||||
# on a non-GitHub environment.
|
|
||||||
pip install pytest-github-actions-annotate-failures==0.1.3
|
|
||||||
- name: Register pytest slow test problem matcher
|
- name: Register pytest slow test problem matcher
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||||
- name: Install SQL Python libraries
|
- name: Install SQL Python libraries
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pip install mysqlclient sqlalchemy_utils
|
uv pip install mysqlclient sqlalchemy_utils
|
||||||
- name: Compile English translations
|
- name: Compile English translations
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
@ -923,10 +912,12 @@ jobs:
|
|||||||
2>&1 | tee pytest-${{ matrix.python-version }}-${mariadb}.txt
|
2>&1 | tee pytest-${{ matrix.python-version }}-${mariadb}.txt
|
||||||
- name: Upload pytest output
|
- name: Upload pytest output
|
||||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||||
uses: actions/upload-artifact@v3.1.2
|
uses: actions/upload-artifact@v4.3.1
|
||||||
with:
|
with:
|
||||||
name: pytest-${{ github.run_number }}
|
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||||
|
steps.pytest-partial.outputs.mariadb }}
|
||||||
path: pytest-*.txt
|
path: pytest-*.txt
|
||||||
|
overwrite: true
|
||||||
- name: Upload coverage artifact
|
- name: Upload coverage artifact
|
||||||
if: needs.info.outputs.skip_coverage != 'true'
|
if: needs.info.outputs.skip_coverage != 'true'
|
||||||
uses: actions/upload-artifact@v4.3.1
|
uses: actions/upload-artifact@v4.3.1
|
||||||
@ -979,16 +970,16 @@ jobs:
|
|||||||
ffmpeg \
|
ffmpeg \
|
||||||
postgresql-server-dev-14
|
postgresql-server-dev-14
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.0
|
uses: actions/cache/restore@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@ -997,20 +988,13 @@ jobs:
|
|||||||
- name: Register Python problem matcher
|
- name: Register Python problem matcher
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||||
- name: Install Pytest Annotation plugin
|
|
||||||
run: |
|
|
||||||
. venv/bin/activate
|
|
||||||
# Ideally this should be part of our dependencies
|
|
||||||
# However this plugin is fairly new and doesn't run correctly
|
|
||||||
# on a non-GitHub environment.
|
|
||||||
pip install pytest-github-actions-annotate-failures==0.1.3
|
|
||||||
- name: Register pytest slow test problem matcher
|
- name: Register pytest slow test problem matcher
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||||
- name: Install SQL Python libraries
|
- name: Install SQL Python libraries
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pip install psycopg2 sqlalchemy_utils
|
uv pip install psycopg2 sqlalchemy_utils
|
||||||
- name: Compile English translations
|
- name: Compile English translations
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
@ -1051,10 +1035,12 @@ jobs:
|
|||||||
2>&1 | tee pytest-${{ matrix.python-version }}-${postgresql}.txt
|
2>&1 | tee pytest-${{ matrix.python-version }}-${postgresql}.txt
|
||||||
- name: Upload pytest output
|
- name: Upload pytest output
|
||||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||||
uses: actions/upload-artifact@v3.1.2
|
uses: actions/upload-artifact@v4.3.1
|
||||||
with:
|
with:
|
||||||
name: pytest-${{ github.run_number }}
|
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||||
|
steps.pytest-partial.outputs.postgresql }}
|
||||||
path: pytest-*.txt
|
path: pytest-*.txt
|
||||||
|
overwrite: true
|
||||||
- name: Upload coverage artifact
|
- name: Upload coverage artifact
|
||||||
if: needs.info.outputs.skip_coverage != 'true'
|
if: needs.info.outputs.skip_coverage != 'true'
|
||||||
uses: actions/upload-artifact@v4.3.1
|
uses: actions/upload-artifact@v4.3.1
|
||||||
@ -1077,14 +1063,14 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
- name: Download all coverage artifacts
|
- name: Download all coverage artifacts
|
||||||
uses: actions/download-artifact@v4.1.3
|
uses: actions/download-artifact@v4.1.4
|
||||||
with:
|
with:
|
||||||
pattern: coverage-*
|
pattern: coverage-*
|
||||||
- name: Upload coverage to Codecov (full coverage)
|
- name: Upload coverage to Codecov (full coverage)
|
||||||
if: needs.info.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
uses: Wandalen/wretry.action@v1.4.4
|
uses: Wandalen/wretry.action@v2.1.0
|
||||||
with:
|
with:
|
||||||
action: codecov/codecov-action@v3.1.3
|
action: codecov/codecov-action@v3.1.3
|
||||||
with: |
|
with: |
|
||||||
@ -1095,7 +1081,7 @@ jobs:
|
|||||||
attempt_delay: 30000
|
attempt_delay: 30000
|
||||||
- name: Upload coverage to Codecov (partial coverage)
|
- name: Upload coverage to Codecov (partial coverage)
|
||||||
if: needs.info.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
uses: Wandalen/wretry.action@v1.4.4
|
uses: Wandalen/wretry.action@v2.1.0
|
||||||
with:
|
with:
|
||||||
action: codecov/codecov-action@v3.1.3
|
action: codecov/codecov-action@v3.1.3
|
||||||
with: |
|
with: |
|
||||||
|
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@ -21,14 +21,14 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3.24.5
|
uses: github/codeql-action/init@v3.24.9
|
||||||
with:
|
with:
|
||||||
languages: python
|
languages: python
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3.24.5
|
uses: github/codeql-action/analyze@v3.24.9
|
||||||
with:
|
with:
|
||||||
category: "/language:python"
|
category: "/language:python"
|
||||||
|
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@ -19,10 +19,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@v5.0.0
|
uses: actions/setup-python@v5.1.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
|
14
.github/workflows/wheels.yml
vendored
14
.github/workflows/wheels.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
architectures: ${{ steps.info.outputs.architectures }}
|
architectures: ${{ steps.info.outputs.architectures }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Get information
|
- name: Get information
|
||||||
id: info
|
id: info
|
||||||
@ -88,15 +88,15 @@ jobs:
|
|||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Download env_file
|
- name: Download env_file
|
||||||
uses: actions/download-artifact@v4.1.3
|
uses: actions/download-artifact@v4.1.4
|
||||||
with:
|
with:
|
||||||
name: env_file
|
name: env_file
|
||||||
|
|
||||||
- name: Download requirements_diff
|
- name: Download requirements_diff
|
||||||
uses: actions/download-artifact@v4.1.3
|
uses: actions/download-artifact@v4.1.4
|
||||||
with:
|
with:
|
||||||
name: requirements_diff
|
name: requirements_diff
|
||||||
|
|
||||||
@ -126,15 +126,15 @@ jobs:
|
|||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.2
|
||||||
|
|
||||||
- name: Download env_file
|
- name: Download env_file
|
||||||
uses: actions/download-artifact@v4.1.3
|
uses: actions/download-artifact@v4.1.4
|
||||||
with:
|
with:
|
||||||
name: env_file
|
name: env_file
|
||||||
|
|
||||||
- name: Download requirements_diff
|
- name: Download requirements_diff
|
||||||
uses: actions/download-artifact@v4.1.3
|
uses: actions/download-artifact@v4.1.4
|
||||||
with:
|
with:
|
||||||
name: requirements_diff
|
name: requirements_diff
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.2.1
|
rev: v0.3.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args:
|
args:
|
||||||
@ -8,11 +8,11 @@ repos:
|
|||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
files: ^((homeassistant|pylint|script|tests)/.+)?[^/]+\.py$
|
files: ^((homeassistant|pylint|script|tests)/.+)?[^/]+\.py$
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.2.2
|
rev: v2.2.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
args:
|
args:
|
||||||
- --ignore-words-list=additionals,alle,alot,bund,currenty,datas,farenheit,falsy,fo,haa,hass,iif,incomfort,ines,ist,nam,nd,pres,pullrequests,resset,rime,ser,serie,te,technik,ue,unsecure,withing,zar
|
- --ignore-words-list=additionals,alle,alot,astroid,bund,caf,convencional,currenty,datas,farenheit,falsy,fo,frequence,haa,hass,iif,incomfort,ines,ist,nam,nd,pres,pullrequests,resset,rime,ser,serie,te,technik,ue,unsecure,vor,withing,zar
|
||||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
exclude_types: [csv, json]
|
exclude_types: [csv, json]
|
||||||
@ -30,7 +30,7 @@ repos:
|
|||||||
- --branch=master
|
- --branch=master
|
||||||
- --branch=rc
|
- --branch=rc
|
||||||
- repo: https://github.com/adrienverge/yamllint.git
|
- repo: https://github.com/adrienverge/yamllint.git
|
||||||
rev: v1.32.0
|
rev: v1.35.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamllint
|
- id: yamllint
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
*.md
|
*.md
|
||||||
.strict-typing
|
.strict-typing
|
||||||
azure-*.yml
|
|
||||||
homeassistant/components/*/translations/*.json
|
homeassistant/components/*/translations/*.json
|
||||||
homeassistant/generated/*
|
homeassistant/generated/*
|
||||||
tests/components/lidarr/fixtures/initialize.js
|
tests/components/lidarr/fixtures/initialize.js
|
||||||
|
@ -228,6 +228,7 @@ homeassistant.components.homekit_controller.select
|
|||||||
homeassistant.components.homekit_controller.storage
|
homeassistant.components.homekit_controller.storage
|
||||||
homeassistant.components.homekit_controller.utils
|
homeassistant.components.homekit_controller.utils
|
||||||
homeassistant.components.homewizard.*
|
homeassistant.components.homewizard.*
|
||||||
|
homeassistant.components.homeworks.*
|
||||||
homeassistant.components.http.*
|
homeassistant.components.http.*
|
||||||
homeassistant.components.huawei_lte.*
|
homeassistant.components.huawei_lte.*
|
||||||
homeassistant.components.humidifier.*
|
homeassistant.components.humidifier.*
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
ignore: |
|
ignore: |
|
||||||
azure-*.yml
|
|
||||||
tests/fixtures/core/config/yaml_errors/
|
tests/fixtures/core/config/yaml_errors/
|
||||||
rules:
|
rules:
|
||||||
braces:
|
braces:
|
||||||
|
23
CODEOWNERS
23
CODEOWNERS
@ -309,14 +309,16 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/doorbird/ @oblogic7 @bdraco @flacjacket
|
/tests/components/doorbird/ @oblogic7 @bdraco @flacjacket
|
||||||
/homeassistant/components/dormakaba_dkey/ @emontnemery
|
/homeassistant/components/dormakaba_dkey/ @emontnemery
|
||||||
/tests/components/dormakaba_dkey/ @emontnemery
|
/tests/components/dormakaba_dkey/ @emontnemery
|
||||||
|
/homeassistant/components/downloader/ @erwindouna
|
||||||
|
/tests/components/downloader/ @erwindouna
|
||||||
/homeassistant/components/dremel_3d_printer/ @tkdrob
|
/homeassistant/components/dremel_3d_printer/ @tkdrob
|
||||||
/tests/components/dremel_3d_printer/ @tkdrob
|
/tests/components/dremel_3d_printer/ @tkdrob
|
||||||
/homeassistant/components/drop_connect/ @ChandlerSystems @pfrazer
|
/homeassistant/components/drop_connect/ @ChandlerSystems @pfrazer
|
||||||
/tests/components/drop_connect/ @ChandlerSystems @pfrazer
|
/tests/components/drop_connect/ @ChandlerSystems @pfrazer
|
||||||
/homeassistant/components/dsmr/ @Robbie1221 @frenck
|
/homeassistant/components/dsmr/ @Robbie1221 @frenck
|
||||||
/tests/components/dsmr/ @Robbie1221 @frenck
|
/tests/components/dsmr/ @Robbie1221 @frenck
|
||||||
/homeassistant/components/dsmr_reader/ @depl0y @glodenox
|
/homeassistant/components/dsmr_reader/ @sorted-bits @glodenox
|
||||||
/tests/components/dsmr_reader/ @depl0y @glodenox
|
/tests/components/dsmr_reader/ @sorted-bits @glodenox
|
||||||
/homeassistant/components/duotecno/ @cereal2nd
|
/homeassistant/components/duotecno/ @cereal2nd
|
||||||
/tests/components/duotecno/ @cereal2nd
|
/tests/components/duotecno/ @cereal2nd
|
||||||
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @andarotajo
|
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @andarotajo
|
||||||
@ -453,6 +455,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/frontier_silicon/ @wlcrs
|
/tests/components/frontier_silicon/ @wlcrs
|
||||||
/homeassistant/components/fully_kiosk/ @cgarwood
|
/homeassistant/components/fully_kiosk/ @cgarwood
|
||||||
/tests/components/fully_kiosk/ @cgarwood
|
/tests/components/fully_kiosk/ @cgarwood
|
||||||
|
/homeassistant/components/fyta/ @dontinelli
|
||||||
|
/tests/components/fyta/ @dontinelli
|
||||||
/homeassistant/components/garages_amsterdam/ @klaasnicolaas
|
/homeassistant/components/garages_amsterdam/ @klaasnicolaas
|
||||||
/tests/components/garages_amsterdam/ @klaasnicolaas
|
/tests/components/garages_amsterdam/ @klaasnicolaas
|
||||||
/homeassistant/components/gardena_bluetooth/ @elupus
|
/homeassistant/components/gardena_bluetooth/ @elupus
|
||||||
@ -568,8 +572,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/homekit/ @bdraco
|
/tests/components/homekit/ @bdraco
|
||||||
/homeassistant/components/homekit_controller/ @Jc2k @bdraco
|
/homeassistant/components/homekit_controller/ @Jc2k @bdraco
|
||||||
/tests/components/homekit_controller/ @Jc2k @bdraco
|
/tests/components/homekit_controller/ @Jc2k @bdraco
|
||||||
/homeassistant/components/homematic/ @pvizeli @danielperna84
|
/homeassistant/components/homematic/ @pvizeli
|
||||||
/tests/components/homematic/ @pvizeli @danielperna84
|
/tests/components/homematic/ @pvizeli
|
||||||
/homeassistant/components/homewizard/ @DCSBL
|
/homeassistant/components/homewizard/ @DCSBL
|
||||||
/tests/components/homewizard/ @DCSBL
|
/tests/components/homewizard/ @DCSBL
|
||||||
/homeassistant/components/honeywell/ @rdfurman @mkmer
|
/homeassistant/components/honeywell/ @rdfurman @mkmer
|
||||||
@ -837,6 +841,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/mopeka/ @bdraco
|
/tests/components/mopeka/ @bdraco
|
||||||
/homeassistant/components/motion_blinds/ @starkillerOG
|
/homeassistant/components/motion_blinds/ @starkillerOG
|
||||||
/tests/components/motion_blinds/ @starkillerOG
|
/tests/components/motion_blinds/ @starkillerOG
|
||||||
|
/homeassistant/components/motionblinds_ble/ @LennP @jerrybboy
|
||||||
|
/tests/components/motionblinds_ble/ @LennP @jerrybboy
|
||||||
/homeassistant/components/motioneye/ @dermotduffy
|
/homeassistant/components/motioneye/ @dermotduffy
|
||||||
/tests/components/motioneye/ @dermotduffy
|
/tests/components/motioneye/ @dermotduffy
|
||||||
/homeassistant/components/motionmount/ @RJPoelstra
|
/homeassistant/components/motionmount/ @RJPoelstra
|
||||||
@ -860,8 +866,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/nam/ @bieniu
|
/tests/components/nam/ @bieniu
|
||||||
/homeassistant/components/nanoleaf/ @milanmeu
|
/homeassistant/components/nanoleaf/ @milanmeu
|
||||||
/tests/components/nanoleaf/ @milanmeu
|
/tests/components/nanoleaf/ @milanmeu
|
||||||
/homeassistant/components/neato/ @dshokouhi @Santobert
|
/homeassistant/components/neato/ @Santobert
|
||||||
/tests/components/neato/ @dshokouhi @Santobert
|
/tests/components/neato/ @Santobert
|
||||||
/homeassistant/components/nederlandse_spoorwegen/ @YarmoM
|
/homeassistant/components/nederlandse_spoorwegen/ @YarmoM
|
||||||
/homeassistant/components/ness_alarm/ @nickw444
|
/homeassistant/components/ness_alarm/ @nickw444
|
||||||
/tests/components/ness_alarm/ @nickw444
|
/tests/components/ness_alarm/ @nickw444
|
||||||
@ -927,6 +933,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/octoprint/ @rfleming71
|
/homeassistant/components/octoprint/ @rfleming71
|
||||||
/tests/components/octoprint/ @rfleming71
|
/tests/components/octoprint/ @rfleming71
|
||||||
/homeassistant/components/ohmconnect/ @robbiet480
|
/homeassistant/components/ohmconnect/ @robbiet480
|
||||||
|
/homeassistant/components/ollama/ @synesthesiam
|
||||||
|
/tests/components/ollama/ @synesthesiam
|
||||||
/homeassistant/components/ombi/ @larssont
|
/homeassistant/components/ombi/ @larssont
|
||||||
/homeassistant/components/omnilogic/ @oliver84 @djtimca @gentoosu
|
/homeassistant/components/omnilogic/ @oliver84 @djtimca @gentoosu
|
||||||
/tests/components/omnilogic/ @oliver84 @djtimca @gentoosu
|
/tests/components/omnilogic/ @oliver84 @djtimca @gentoosu
|
||||||
@ -1095,7 +1103,6 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/recovery_mode/ @home-assistant/core
|
/tests/components/recovery_mode/ @home-assistant/core
|
||||||
/homeassistant/components/refoss/ @ashionky
|
/homeassistant/components/refoss/ @ashionky
|
||||||
/tests/components/refoss/ @ashionky
|
/tests/components/refoss/ @ashionky
|
||||||
/homeassistant/components/rejseplanen/ @DarkFox
|
|
||||||
/homeassistant/components/remote/ @home-assistant/core
|
/homeassistant/components/remote/ @home-assistant/core
|
||||||
/tests/components/remote/ @home-assistant/core
|
/tests/components/remote/ @home-assistant/core
|
||||||
/homeassistant/components/renault/ @epenet
|
/homeassistant/components/renault/ @epenet
|
||||||
@ -1191,6 +1198,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/senz/ @milanmeu
|
/tests/components/senz/ @milanmeu
|
||||||
/homeassistant/components/serial/ @fabaff
|
/homeassistant/components/serial/ @fabaff
|
||||||
/homeassistant/components/seven_segments/ @fabaff
|
/homeassistant/components/seven_segments/ @fabaff
|
||||||
|
/homeassistant/components/seventeentrack/ @shaiu
|
||||||
|
/tests/components/seventeentrack/ @shaiu
|
||||||
/homeassistant/components/sfr_box/ @epenet
|
/homeassistant/components/sfr_box/ @epenet
|
||||||
/tests/components/sfr_box/ @epenet
|
/tests/components/sfr_box/ @epenet
|
||||||
/homeassistant/components/sharkiq/ @JeffResc @funkybunch
|
/homeassistant/components/sharkiq/ @JeffResc @funkybunch
|
||||||
|
28
Dockerfile
28
Dockerfile
@ -6,47 +6,47 @@ FROM ${BUILD_FROM}
|
|||||||
|
|
||||||
# Synchronize with homeassistant/core.py:async_stop
|
# Synchronize with homeassistant/core.py:async_stop
|
||||||
ENV \
|
ENV \
|
||||||
S6_SERVICES_GRACETIME=240000
|
S6_SERVICES_GRACETIME=240000 \
|
||||||
|
UV_SYSTEM_PYTHON=true
|
||||||
|
|
||||||
ARG QEMU_CPU
|
ARG QEMU_CPU
|
||||||
|
|
||||||
|
# Install uv
|
||||||
|
RUN pip3 install uv==0.1.24
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
## Setup Home Assistant Core dependencies
|
## Setup Home Assistant Core dependencies
|
||||||
COPY requirements.txt homeassistant/
|
COPY requirements.txt homeassistant/
|
||||||
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
|
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
|
||||||
RUN \
|
RUN \
|
||||||
pip3 install \
|
uv pip install \
|
||||||
--only-binary=:all: \
|
--no-build \
|
||||||
-r homeassistant/requirements.txt
|
-r homeassistant/requirements.txt
|
||||||
|
|
||||||
COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/
|
COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/
|
||||||
RUN \
|
RUN \
|
||||||
if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \
|
if ls homeassistant/home_assistant_*.whl 1> /dev/null 2>&1; then \
|
||||||
pip3 install homeassistant/home_assistant_frontend-*.whl; \
|
uv pip install homeassistant/home_assistant_*.whl; \
|
||||||
fi \
|
|
||||||
&& if ls homeassistant/home_assistant_intents*.whl 1> /dev/null 2>&1; then \
|
|
||||||
pip3 install homeassistant/home_assistant_intents-*.whl; \
|
|
||||||
fi \
|
fi \
|
||||||
&& if [ "${BUILD_ARCH}" = "i386" ]; then \
|
&& if [ "${BUILD_ARCH}" = "i386" ]; then \
|
||||||
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
|
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
|
||||||
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
|
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
|
||||||
linux32 pip3 install \
|
linux32 uv pip install \
|
||||||
--only-binary=:all: \
|
--no-build \
|
||||||
-r homeassistant/requirements_all.txt; \
|
-r homeassistant/requirements_all.txt; \
|
||||||
else \
|
else \
|
||||||
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
|
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
|
||||||
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
|
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
|
||||||
pip3 install \
|
uv pip install \
|
||||||
--only-binary=:all: \
|
--no-build \
|
||||||
-r homeassistant/requirements_all.txt; \
|
-r homeassistant/requirements_all.txt; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Setup Home Assistant Core
|
## Setup Home Assistant Core
|
||||||
COPY . homeassistant/
|
COPY . homeassistant/
|
||||||
RUN \
|
RUN \
|
||||||
pip3 install \
|
uv pip install \
|
||||||
--only-binary=:all: \
|
|
||||||
-e ./homeassistant \
|
-e ./homeassistant \
|
||||||
&& python3 -m compileall \
|
&& python3 -m compileall \
|
||||||
homeassistant/homeassistant
|
homeassistant/homeassistant
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11
|
FROM mcr.microsoft.com/devcontainers/python:1-3.12
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
10
build.yaml
10
build.yaml
@ -1,10 +1,10 @@
|
|||||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||||
build_from:
|
build_from:
|
||||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.02.1
|
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2024.03.0
|
||||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.02.1
|
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2024.03.0
|
||||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.02.1
|
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2024.03.0
|
||||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.02.1
|
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2024.03.0
|
||||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.02.1
|
i386: ghcr.io/home-assistant/i386-homeassistant-base:2024.03.0
|
||||||
codenotary:
|
codenotary:
|
||||||
signer: notary@home-assistant.io
|
signer: notary@home-assistant.io
|
||||||
base_image: notary@home-assistant.io
|
base_image: notary@home-assistant.io
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Start Home Assistant."""
|
"""Start Home Assistant."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Provide an authentication layer for Home Assistant."""
|
"""Provide an authentication layer for Home Assistant."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -19,13 +20,13 @@ from homeassistant.core import (
|
|||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
callback,
|
callback,
|
||||||
)
|
)
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import auth_store, jwt_wrapper, models
|
from . import auth_store, jwt_wrapper, models
|
||||||
from .const import ACCESS_TOKEN_EXPIRATION, GROUP_ID_ADMIN, REFRESH_TOKEN_EXPIRATION
|
from .const import ACCESS_TOKEN_EXPIRATION, GROUP_ID_ADMIN, REFRESH_TOKEN_EXPIRATION
|
||||||
from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config
|
from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config
|
||||||
|
from .models import AuthFlowResult
|
||||||
from .providers import AuthProvider, LoginFlow, auth_provider_from_config
|
from .providers import AuthProvider, LoginFlow, auth_provider_from_config
|
||||||
|
|
||||||
EVENT_USER_ADDED = "user_added"
|
EVENT_USER_ADDED = "user_added"
|
||||||
@ -88,9 +89,13 @@ async def auth_manager_from_config(
|
|||||||
return manager
|
return manager
|
||||||
|
|
||||||
|
|
||||||
class AuthManagerFlowManager(data_entry_flow.FlowManager):
|
class AuthManagerFlowManager(
|
||||||
|
data_entry_flow.FlowManager[AuthFlowResult, tuple[str, str]]
|
||||||
|
):
|
||||||
"""Manage authentication flows."""
|
"""Manage authentication flows."""
|
||||||
|
|
||||||
|
_flow_result = AuthFlowResult
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, auth_manager: AuthManager) -> None:
|
def __init__(self, hass: HomeAssistant, auth_manager: AuthManager) -> None:
|
||||||
"""Init auth manager flows."""
|
"""Init auth manager flows."""
|
||||||
super().__init__(hass)
|
super().__init__(hass)
|
||||||
@ -98,11 +103,11 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager):
|
|||||||
|
|
||||||
async def async_create_flow(
|
async def async_create_flow(
|
||||||
self,
|
self,
|
||||||
handler_key: str,
|
handler_key: tuple[str, str],
|
||||||
*,
|
*,
|
||||||
context: dict[str, Any] | None = None,
|
context: dict[str, Any] | None = None,
|
||||||
data: dict[str, Any] | None = None,
|
data: dict[str, Any] | None = None,
|
||||||
) -> data_entry_flow.FlowHandler:
|
) -> LoginFlow:
|
||||||
"""Create a login flow."""
|
"""Create a login flow."""
|
||||||
auth_provider = self.auth_manager.get_auth_provider(*handler_key)
|
auth_provider = self.auth_manager.get_auth_provider(*handler_key)
|
||||||
if not auth_provider:
|
if not auth_provider:
|
||||||
@ -110,8 +115,10 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager):
|
|||||||
return await auth_provider.async_login_flow(context)
|
return await auth_provider.async_login_flow(context)
|
||||||
|
|
||||||
async def async_finish_flow(
|
async def async_finish_flow(
|
||||||
self, flow: data_entry_flow.FlowHandler, result: FlowResult
|
self,
|
||||||
) -> FlowResult:
|
flow: data_entry_flow.FlowHandler[AuthFlowResult, tuple[str, str]],
|
||||||
|
result: AuthFlowResult,
|
||||||
|
) -> AuthFlowResult:
|
||||||
"""Return a user as result of login flow."""
|
"""Return a user as result of login flow."""
|
||||||
flow = cast(LoginFlow, flow)
|
flow = cast(LoginFlow, flow)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Storage for auth models."""
|
"""Storage for auth models."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -30,6 +31,17 @@ GROUP_NAME_ADMIN = "Administrators"
|
|||||||
GROUP_NAME_USER = "Users"
|
GROUP_NAME_USER = "Users"
|
||||||
GROUP_NAME_READ_ONLY = "Read Only"
|
GROUP_NAME_READ_ONLY = "Read Only"
|
||||||
|
|
||||||
|
# We always save the auth store after we load it since
|
||||||
|
# we may migrate data and do not want to have to do it again
|
||||||
|
# but we don't want to do it during startup so we schedule
|
||||||
|
# the first save 5 minutes out knowing something else may
|
||||||
|
# want to save the auth store before then, and since Storage
|
||||||
|
# will honor the lower of the two delays, it will save it
|
||||||
|
# faster if something else saves it.
|
||||||
|
INITIAL_LOAD_SAVE_DELAY = 300
|
||||||
|
|
||||||
|
DEFAULT_SAVE_DELAY = 1
|
||||||
|
|
||||||
|
|
||||||
class AuthStore:
|
class AuthStore:
|
||||||
"""Stores authentication info.
|
"""Stores authentication info.
|
||||||
@ -467,12 +479,12 @@ class AuthStore:
|
|||||||
self._groups = groups
|
self._groups = groups
|
||||||
self._users = users
|
self._users = users
|
||||||
|
|
||||||
self._async_schedule_save()
|
self._async_schedule_save(INITIAL_LOAD_SAVE_DELAY)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_schedule_save(self) -> None:
|
def _async_schedule_save(self, delay: float = DEFAULT_SAVE_DELAY) -> None:
|
||||||
"""Save users."""
|
"""Save users."""
|
||||||
self._store.async_delay_save(self._data_to_save, 1)
|
self._store.async_delay_save(self._data_to_save, delay)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
|
def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Constants for the auth module."""
|
"""Constants for the auth module."""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
|
ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
|
||||||
|
@ -4,6 +4,7 @@ Since we decode the same tokens over and over again
|
|||||||
we can cache the result of the decode of valid tokens
|
we can cache the result of the decode of valid tokens
|
||||||
to speed up the process.
|
to speed up the process.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Pluggable auth modules for Home Assistant."""
|
"""Pluggable auth modules for Home Assistant."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import importlib
|
|
||||||
import logging
|
import logging
|
||||||
import types
|
import types
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -14,6 +14,7 @@ from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.importlib import async_import_module
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
|
|
||||||
MULTI_FACTOR_AUTH_MODULES: Registry[str, type[MultiFactorAuthModule]] = Registry()
|
MULTI_FACTOR_AUTH_MODULES: Registry[str, type[MultiFactorAuthModule]] = Registry()
|
||||||
@ -148,7 +149,7 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.Modul
|
|||||||
module_path = f"homeassistant.auth.mfa_modules.{module_name}"
|
module_path = f"homeassistant.auth.mfa_modules.{module_name}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(module_path)
|
module = await async_import_module(hass, module_path)
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
_LOGGER.error("Unable to load mfa module %s: %s", module_name, err)
|
_LOGGER.error("Unable to load mfa module %s: %s", module_name, err)
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Example auth module."""
|
"""Example auth module."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Sending HOTP through notify service
|
Sending HOTP through notify service
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Time-based One Time Password auth module."""
|
"""Time-based One Time Password auth module."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Auth models."""
|
"""Auth models."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@ -11,6 +12,7 @@ from attr import Attribute
|
|||||||
from attr.setters import validate
|
from attr.setters import validate
|
||||||
|
|
||||||
from homeassistant.const import __version__
|
from homeassistant.const import __version__
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import permissions as perm_mdl
|
from . import permissions as perm_mdl
|
||||||
@ -26,6 +28,8 @@ TOKEN_TYPE_NORMAL = "normal"
|
|||||||
TOKEN_TYPE_SYSTEM = "system"
|
TOKEN_TYPE_SYSTEM = "system"
|
||||||
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token"
|
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token"
|
||||||
|
|
||||||
|
AuthFlowResult = FlowResult[tuple[str, str]]
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True)
|
@attr.s(slots=True)
|
||||||
class Group:
|
class Group:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Permissions for Home Assistant."""
|
"""Permissions for Home Assistant."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Permission constants."""
|
"""Permission constants."""
|
||||||
|
|
||||||
CAT_ENTITIES = "entities"
|
CAT_ENTITIES = "entities"
|
||||||
CAT_CONFIG_ENTRIES = "config_entries"
|
CAT_CONFIG_ENTRIES = "config_entries"
|
||||||
SUBCAT_ALL = "all"
|
SUBCAT_ALL = "all"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Entity permissions."""
|
"""Entity permissions."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Permission for events."""
|
"""Permission for events."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Merging of policies."""
|
"""Merging of policies."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
@ -57,10 +58,7 @@ def _merge_policies(sources: list[CategoryType]) -> CategoryType:
|
|||||||
continue
|
continue
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
|
|
||||||
key_sources = []
|
key_sources = [src.get(key) for src in sources if isinstance(src, dict)]
|
||||||
for src in sources:
|
|
||||||
if isinstance(src, dict):
|
|
||||||
key_sources.append(src.get(key))
|
|
||||||
|
|
||||||
policy[key] = _merge_policies(key_sources)
|
policy[key] = _merge_policies(key_sources)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Models for permissions."""
|
"""Models for permissions."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""System policies."""
|
"""System policies."""
|
||||||
|
|
||||||
from .const import CAT_ENTITIES, POLICY_READ, SUBCAT_ALL
|
from .const import CAT_ENTITIES, POLICY_READ, SUBCAT_ALL
|
||||||
|
|
||||||
ADMIN_POLICY = {CAT_ENTITIES: True}
|
ADMIN_POLICY = {CAT_ENTITIES: True}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Common code for permissions."""
|
"""Common code for permissions."""
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
|
|
||||||
# MyPy doesn't support recursion yet. So writing it out as far as we need.
|
# MyPy doesn't support recursion yet. So writing it out as far as we need.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Helpers to deal with permissions."""
|
"""Helpers to deal with permissions."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""Auth providers for Home Assistant."""
|
"""Auth providers for Home Assistant."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
import importlib
|
|
||||||
import logging
|
import logging
|
||||||
import types
|
import types
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -13,14 +13,14 @@ from voluptuous.humanize import humanize_error
|
|||||||
from homeassistant import data_entry_flow, requirements
|
from homeassistant import data_entry_flow, requirements
|
||||||
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
|
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.importlib import async_import_module
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
|
|
||||||
from ..auth_store import AuthStore
|
from ..auth_store import AuthStore
|
||||||
from ..const import MFA_SESSION_EXPIRATION
|
from ..const import MFA_SESSION_EXPIRATION
|
||||||
from ..models import Credentials, RefreshToken, User, UserMeta
|
from ..models import AuthFlowResult, Credentials, RefreshToken, User, UserMeta
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
DATA_REQS = "auth_prov_reqs_processed"
|
DATA_REQS = "auth_prov_reqs_processed"
|
||||||
@ -157,7 +157,9 @@ async def load_auth_provider_module(
|
|||||||
) -> types.ModuleType:
|
) -> types.ModuleType:
|
||||||
"""Load an auth provider."""
|
"""Load an auth provider."""
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(f"homeassistant.auth.providers.{provider}")
|
module = await async_import_module(
|
||||||
|
hass, f"homeassistant.auth.providers.{provider}"
|
||||||
|
)
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
_LOGGER.error("Unable to load auth provider %s: %s", provider, err)
|
_LOGGER.error("Unable to load auth provider %s: %s", provider, err)
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
@ -181,9 +183,11 @@ async def load_auth_provider_module(
|
|||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
class LoginFlow(data_entry_flow.FlowHandler):
|
class LoginFlow(data_entry_flow.FlowHandler[AuthFlowResult, tuple[str, str]]):
|
||||||
"""Handler for the login flow."""
|
"""Handler for the login flow."""
|
||||||
|
|
||||||
|
_flow_result = AuthFlowResult
|
||||||
|
|
||||||
def __init__(self, auth_provider: AuthProvider) -> None:
|
def __init__(self, auth_provider: AuthProvider) -> None:
|
||||||
"""Initialize the login flow."""
|
"""Initialize the login flow."""
|
||||||
self._auth_provider = auth_provider
|
self._auth_provider = auth_provider
|
||||||
@ -197,7 +201,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
|||||||
|
|
||||||
async def async_step_init(
|
async def async_step_init(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> AuthFlowResult:
|
||||||
"""Handle the first step of login flow.
|
"""Handle the first step of login flow.
|
||||||
|
|
||||||
Return self.async_show_form(step_id='init') if user_input is None.
|
Return self.async_show_form(step_id='init') if user_input is None.
|
||||||
@ -207,7 +211,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
|||||||
|
|
||||||
async def async_step_select_mfa_module(
|
async def async_step_select_mfa_module(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> AuthFlowResult:
|
||||||
"""Handle the step of select mfa module."""
|
"""Handle the step of select mfa module."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
@ -232,7 +236,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
|||||||
|
|
||||||
async def async_step_mfa(
|
async def async_step_mfa(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> AuthFlowResult:
|
||||||
"""Handle the step of mfa validation."""
|
"""Handle the step of mfa validation."""
|
||||||
assert self.credential
|
assert self.credential
|
||||||
assert self.user
|
assert self.user
|
||||||
@ -282,6 +286,6 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
|||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_finish(self, flow_result: Any) -> FlowResult:
|
async def async_finish(self, flow_result: Any) -> AuthFlowResult:
|
||||||
"""Handle the pass of login flow."""
|
"""Handle the pass of login flow."""
|
||||||
return self.async_create_entry(data=flow_result)
|
return self.async_create_entry(data=flow_result)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Auth provider that validates credentials via an external command."""
|
"""Auth provider that validates credentials via an external command."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -10,10 +11,9 @@ from typing import Any, cast
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import CONF_COMMAND
|
from homeassistant.const import CONF_COMMAND
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import AuthFlowResult, Credentials, UserMeta
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
CONF_ARGS = "args"
|
CONF_ARGS = "args"
|
||||||
@ -138,7 +138,7 @@ class CommandLineLoginFlow(LoginFlow):
|
|||||||
|
|
||||||
async def async_step_init(
|
async def async_step_init(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> AuthFlowResult:
|
||||||
"""Handle the step of the form."""
|
"""Handle the step of the form."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Home Assistant auth provider."""
|
"""Home Assistant auth provider."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -12,11 +13,10 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.const import CONF_ID
|
from homeassistant.const import CONF_ID
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import AuthFlowResult, Credentials, UserMeta
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
STORAGE_VERSION = 1
|
STORAGE_VERSION = 1
|
||||||
@ -321,7 +321,7 @@ class HassLoginFlow(LoginFlow):
|
|||||||
|
|
||||||
async def async_step_init(
|
async def async_step_init(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> AuthFlowResult:
|
||||||
"""Handle the step of the form."""
|
"""Handle the step of the form."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Example auth provider."""
|
"""Example auth provider."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
@ -8,10 +9,9 @@ from typing import Any, cast
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import AuthFlowResult, Credentials, UserMeta
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
USER_SCHEMA = vol.Schema(
|
USER_SCHEMA = vol.Schema(
|
||||||
@ -98,7 +98,7 @@ class ExampleLoginFlow(LoginFlow):
|
|||||||
|
|
||||||
async def async_step_init(
|
async def async_step_init(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> AuthFlowResult:
|
||||||
"""Handle the step of the form."""
|
"""Handle the step of the form."""
|
||||||
errors = None
|
errors = None
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
It will be removed when auth system production ready
|
It will be removed when auth system production ready
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
@ -11,12 +12,11 @@ from typing import Any, cast
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.core import async_get_hass, callback
|
from homeassistant.core import async_get_hass, callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
|
|
||||||
from ..models import Credentials, UserMeta
|
from ..models import AuthFlowResult, Credentials, UserMeta
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
||||||
@ -101,7 +101,7 @@ class LegacyLoginFlow(LoginFlow):
|
|||||||
|
|
||||||
async def async_step_init(
|
async def async_step_init(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> AuthFlowResult:
|
||||||
"""Handle the step of the form."""
|
"""Handle the step of the form."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
It shows list of users if access from trusted network.
|
It shows list of users if access from trusted network.
|
||||||
Abort login flow if not access from trusted network.
|
Abort login flow if not access from trusted network.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
@ -19,13 +20,12 @@ from typing import Any, cast
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.network import is_cloud_connection
|
from homeassistant.helpers.network import is_cloud_connection
|
||||||
|
|
||||||
from .. import InvalidAuthError
|
from .. import InvalidAuthError
|
||||||
from ..models import Credentials, RefreshToken, UserMeta
|
from ..models import AuthFlowResult, Credentials, RefreshToken, UserMeta
|
||||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||||
|
|
||||||
IPAddress = IPv4Address | IPv6Address
|
IPAddress = IPv4Address | IPv6Address
|
||||||
@ -226,7 +226,7 @@ class TrustedNetworksLoginFlow(LoginFlow):
|
|||||||
|
|
||||||
async def async_step_init(
|
async def async_step_init(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> AuthFlowResult:
|
||||||
"""Handle the step of the form."""
|
"""Handle the step of the form."""
|
||||||
try:
|
try:
|
||||||
cast(
|
cast(
|
||||||
|
@ -6,6 +6,7 @@ Since we have dropped support for Python 3.10, we can remove this backport.
|
|||||||
This file is kept for now to avoid breaking custom components that might
|
This file is kept for now to avoid breaking custom components that might
|
||||||
import it.
|
import it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
|
@ -41,12 +41,10 @@ class cached_property(Generic[_T]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __get__(self, instance: None, owner: type[Any] | None = None) -> Self:
|
def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...
|
||||||
...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __get__(self, instance: Any, owner: type[Any] | None = None) -> _T:
|
def __get__(self, instance: Any, owner: type[Any] | None = None) -> _T: ...
|
||||||
...
|
|
||||||
|
|
||||||
def __get__(
|
def __get__(
|
||||||
self, instance: Any | None, owner: type[Any] | None = None
|
self, instance: Any | None, owner: type[Any] | None = None
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Block blocking calls being done in asyncio."""
|
"""Block blocking calls being done in asyncio."""
|
||||||
|
|
||||||
from http.client import HTTPConnection
|
from http.client import HTTPConnection
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
"""Provide methods to bootstrap a Home Assistant instance."""
|
"""Provide methods to bootstrap a Home Assistant instance."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from collections import defaultdict
|
||||||
import contextlib
|
import contextlib
|
||||||
from datetime import timedelta
|
from functools import partial
|
||||||
|
from itertools import chain
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
from operator import itemgetter
|
from operator import contains, itemgetter
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
@ -22,17 +25,35 @@ import yarl
|
|||||||
|
|
||||||
from . import config as conf_util, config_entries, core, loader, requirements
|
from . import config as conf_util, config_entries, core, loader, requirements
|
||||||
|
|
||||||
# Pre-import config and lovelace which have no requirements here to avoid
|
# Pre-import frontend deps which have no requirements here to avoid
|
||||||
# loading them at run time and blocking the event loop. We do this ahead
|
# loading them at run time and blocking the event loop. We do this ahead
|
||||||
# of time so that we do not have to flag frontends deps with `import_executor`
|
# of time so that we do not have to flag frontend deps with `import_executor`
|
||||||
# as it would create a thundering heard of executor jobs trying to import
|
# as it would create a thundering heard of executor jobs trying to import
|
||||||
# frontend deps at the same time.
|
# frontend deps at the same time.
|
||||||
from .components import (
|
from .components import (
|
||||||
api as api_pre_import, # noqa: F401
|
api as api_pre_import, # noqa: F401
|
||||||
|
auth as auth_pre_import, # noqa: F401
|
||||||
config as config_pre_import, # noqa: F401
|
config as config_pre_import, # noqa: F401
|
||||||
http,
|
default_config as default_config_pre_import, # noqa: F401
|
||||||
|
device_automation as device_automation_pre_import, # noqa: F401
|
||||||
|
diagnostics as diagnostics_pre_import, # noqa: F401
|
||||||
|
file_upload as file_upload_pre_import, # noqa: F401
|
||||||
|
group as group_pre_import, # noqa: F401
|
||||||
|
history as history_pre_import, # noqa: F401
|
||||||
|
http, # not named pre_import since it has requirements
|
||||||
|
image_upload as image_upload_import, # noqa: F401 - not named pre_import since it has requirements
|
||||||
|
logbook as logbook_pre_import, # noqa: F401
|
||||||
lovelace as lovelace_pre_import, # noqa: F401
|
lovelace as lovelace_pre_import, # noqa: F401
|
||||||
|
onboarding as onboarding_pre_import, # noqa: F401
|
||||||
|
recorder as recorder_import, # noqa: F401 - not named pre_import since it has requirements
|
||||||
|
repairs as repairs_pre_import, # noqa: F401
|
||||||
|
search as search_pre_import, # noqa: F401
|
||||||
|
sensor as sensor_pre_import, # noqa: F401
|
||||||
|
system_log as system_log_pre_import, # noqa: F401
|
||||||
|
webhook as webhook_pre_import, # noqa: F401
|
||||||
|
websocket_api as websocket_api_pre_import, # noqa: F401
|
||||||
)
|
)
|
||||||
|
from .components.sensor import recorder as sensor_recorder # noqa: F401
|
||||||
from .const import (
|
from .const import (
|
||||||
FORMAT_DATETIME,
|
FORMAT_DATETIME,
|
||||||
KEY_DATA_LOGGING as DATA_LOGGING,
|
KEY_DATA_LOGGING as DATA_LOGGING,
|
||||||
@ -43,6 +64,7 @@ from .const import (
|
|||||||
from .exceptions import HomeAssistantError
|
from .exceptions import HomeAssistantError
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
area_registry,
|
area_registry,
|
||||||
|
category_registry,
|
||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
device_registry,
|
device_registry,
|
||||||
entity,
|
entity,
|
||||||
@ -56,11 +78,13 @@ from .helpers import (
|
|||||||
translation,
|
translation,
|
||||||
)
|
)
|
||||||
from .helpers.dispatcher import async_dispatcher_send
|
from .helpers.dispatcher import async_dispatcher_send
|
||||||
|
from .helpers.storage import get_internal_store_manager
|
||||||
|
from .helpers.system_info import async_get_system_info
|
||||||
from .helpers.typing import ConfigType
|
from .helpers.typing import ConfigType
|
||||||
from .setup import (
|
from .setup import (
|
||||||
BASE_PLATFORMS,
|
BASE_PLATFORMS,
|
||||||
DATA_SETUP_STARTED,
|
DATA_SETUP_STARTED,
|
||||||
DATA_SETUP_TIME,
|
async_get_setup_timings,
|
||||||
async_notify_setup_error,
|
async_notify_setup_error,
|
||||||
async_set_domains_to_be_loaded,
|
async_set_domains_to_be_loaded,
|
||||||
async_setup_component,
|
async_setup_component,
|
||||||
@ -69,11 +93,19 @@ from .util.async_ import create_eager_task
|
|||||||
from .util.logging import async_activate_log_queue_handler
|
from .util.logging import async_activate_log_queue_handler
|
||||||
from .util.package import async_get_user_site, is_virtual_env
|
from .util.package import async_get_user_site, is_virtual_env
|
||||||
|
|
||||||
|
with contextlib.suppress(ImportError):
|
||||||
|
# Ensure anyio backend is imported to avoid it being imported in the event loop
|
||||||
|
from anyio._backends import _asyncio # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .runner import RuntimeConfig
|
from .runner import RuntimeConfig
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SETUP_ORDER_SORT_KEY = partial(contains, BASE_PLATFORMS)
|
||||||
|
|
||||||
|
|
||||||
ERROR_LOG_FILENAME = "home-assistant.log"
|
ERROR_LOG_FILENAME = "home-assistant.log"
|
||||||
|
|
||||||
# hass.data key for logging information.
|
# hass.data key for logging information.
|
||||||
@ -87,7 +119,6 @@ STAGE_2_TIMEOUT = 300
|
|||||||
WRAP_UP_TIMEOUT = 300
|
WRAP_UP_TIMEOUT = 300
|
||||||
COOLDOWN_TIME = 60
|
COOLDOWN_TIME = 60
|
||||||
|
|
||||||
MAX_LOAD_CONCURRENTLY = 6
|
|
||||||
|
|
||||||
DEBUGGER_INTEGRATIONS = {"debugpy"}
|
DEBUGGER_INTEGRATIONS = {"debugpy"}
|
||||||
CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"}
|
CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"}
|
||||||
@ -128,6 +159,7 @@ DEFAULT_INTEGRATIONS = {
|
|||||||
# These integrations are set up unless recovery mode is activated.
|
# These integrations are set up unless recovery mode is activated.
|
||||||
#
|
#
|
||||||
# Integrations providing core functionality:
|
# Integrations providing core functionality:
|
||||||
|
"analytics", # Needed for onboarding
|
||||||
"application_credentials",
|
"application_credentials",
|
||||||
"backup",
|
"backup",
|
||||||
"frontend",
|
"frontend",
|
||||||
@ -168,16 +200,35 @@ CRITICAL_INTEGRATIONS = {
|
|||||||
"frontend",
|
"frontend",
|
||||||
}
|
}
|
||||||
|
|
||||||
SETUP_ORDER = {
|
SETUP_ORDER = (
|
||||||
# Load logging as soon as possible
|
# Load logging as soon as possible
|
||||||
"logging": LOGGING_INTEGRATIONS,
|
("logging", LOGGING_INTEGRATIONS),
|
||||||
# Setup frontend
|
# Setup frontend and recorder
|
||||||
"frontend": FRONTEND_INTEGRATIONS,
|
("frontend, recorder", {*FRONTEND_INTEGRATIONS, *RECORDER_INTEGRATIONS}),
|
||||||
# Setup recorder
|
|
||||||
"recorder": RECORDER_INTEGRATIONS,
|
|
||||||
# Start up debuggers. Start these first in case they want to wait.
|
# Start up debuggers. Start these first in case they want to wait.
|
||||||
"debugger": DEBUGGER_INTEGRATIONS,
|
("debugger", DEBUGGER_INTEGRATIONS),
|
||||||
}
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Storage keys we are likely to load during startup
|
||||||
|
# in order of when we expect to load them.
|
||||||
|
#
|
||||||
|
# If they do not exist they will not be loaded
|
||||||
|
#
|
||||||
|
PRELOAD_STORAGE = [
|
||||||
|
"core.network",
|
||||||
|
"http.auth",
|
||||||
|
"image",
|
||||||
|
"lovelace_dashboards",
|
||||||
|
"lovelace_resources",
|
||||||
|
"core.uuid",
|
||||||
|
"lovelace.map",
|
||||||
|
"bluetooth.passive_update_processor",
|
||||||
|
"bluetooth.remote_scanners",
|
||||||
|
"assist_pipeline.pipelines",
|
||||||
|
"core.analytics",
|
||||||
|
"auth_module.totp",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_hass(
|
async def async_setup_hass(
|
||||||
@ -315,14 +366,16 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None:
|
|||||||
asyncio event loop. By primeing the cache of uname we can
|
asyncio event loop. By primeing the cache of uname we can
|
||||||
avoid the blocking call in the event loop.
|
avoid the blocking call in the event loop.
|
||||||
"""
|
"""
|
||||||
platform.uname().processor # pylint: disable=expression-not-assigned
|
_ = platform.uname().processor
|
||||||
|
|
||||||
# Load the registries and cache the result of platform.uname().processor
|
# Load the registries and cache the result of platform.uname().processor
|
||||||
translation.async_setup(hass)
|
translation.async_setup(hass)
|
||||||
entity.async_setup(hass)
|
entity.async_setup(hass)
|
||||||
template.async_setup(hass)
|
template.async_setup(hass)
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
|
create_eager_task(get_internal_store_manager(hass).async_initialize()),
|
||||||
create_eager_task(area_registry.async_load(hass)),
|
create_eager_task(area_registry.async_load(hass)),
|
||||||
|
create_eager_task(category_registry.async_load(hass)),
|
||||||
create_eager_task(device_registry.async_load(hass)),
|
create_eager_task(device_registry.async_load(hass)),
|
||||||
create_eager_task(entity_registry.async_load(hass)),
|
create_eager_task(entity_registry.async_load(hass)),
|
||||||
create_eager_task(floor_registry.async_load(hass)),
|
create_eager_task(floor_registry.async_load(hass)),
|
||||||
@ -332,6 +385,7 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None:
|
|||||||
create_eager_task(template.async_load_custom_templates(hass)),
|
create_eager_task(template.async_load_custom_templates(hass)),
|
||||||
create_eager_task(restore_state.async_load(hass)),
|
create_eager_task(restore_state.async_load(hass)),
|
||||||
create_eager_task(hass.config_entries.async_initialize()),
|
create_eager_task(hass.config_entries.async_initialize()),
|
||||||
|
create_eager_task(async_get_system_info(hass)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -571,7 +625,9 @@ class _WatchPendingSetups:
|
|||||||
"""Periodic log and dispatch of setups that are pending."""
|
"""Periodic log and dispatch of setups that are pending."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, hass: core.HomeAssistant, setup_started: dict[str, float]
|
self,
|
||||||
|
hass: core.HomeAssistant,
|
||||||
|
setup_started: dict[tuple[str, str | None], float],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the WatchPendingSetups class."""
|
"""Initialize the WatchPendingSetups class."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
@ -586,11 +642,15 @@ class _WatchPendingSetups:
|
|||||||
now = monotonic()
|
now = monotonic()
|
||||||
self._duration_count += SLOW_STARTUP_CHECK_INTERVAL
|
self._duration_count += SLOW_STARTUP_CHECK_INTERVAL
|
||||||
|
|
||||||
remaining_with_setup_started = {
|
remaining_with_setup_started: defaultdict[str, float] = defaultdict(float)
|
||||||
domain: (now - start_time)
|
for integration_group, start_time in self._setup_started.items():
|
||||||
for domain, start_time in self._setup_started.items()
|
domain, _ = integration_group
|
||||||
}
|
remaining_with_setup_started[domain] += now - start_time
|
||||||
_LOGGER.debug("Integration remaining: %s", remaining_with_setup_started)
|
|
||||||
|
if remaining_with_setup_started:
|
||||||
|
_LOGGER.debug("Integration remaining: %s", remaining_with_setup_started)
|
||||||
|
elif waiting_tasks := self._hass._active_tasks: # pylint: disable=protected-access
|
||||||
|
_LOGGER.debug("Waiting on tasks: %s", waiting_tasks)
|
||||||
self._async_dispatch(remaining_with_setup_started)
|
self._async_dispatch(remaining_with_setup_started)
|
||||||
if (
|
if (
|
||||||
self._setup_started
|
self._setup_started
|
||||||
@ -600,7 +660,7 @@ class _WatchPendingSetups:
|
|||||||
# once we take over LOG_SLOW_STARTUP_INTERVAL (60s) to start up
|
# once we take over LOG_SLOW_STARTUP_INTERVAL (60s) to start up
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Waiting on integrations to complete setup: %s",
|
"Waiting on integrations to complete setup: %s",
|
||||||
", ".join(self._setup_started),
|
self._setup_started,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Running timeout Zones: %s", self._hass.timeout.zones)
|
_LOGGER.debug("Running timeout Zones: %s", self._hass.timeout.zones)
|
||||||
@ -640,13 +700,18 @@ async def async_setup_multi_components(
|
|||||||
"""Set up multiple domains. Log on failure."""
|
"""Set up multiple domains. Log on failure."""
|
||||||
# Avoid creating tasks for domains that were setup in a previous stage
|
# Avoid creating tasks for domains that were setup in a previous stage
|
||||||
domains_not_yet_setup = domains - hass.config.components
|
domains_not_yet_setup = domains - hass.config.components
|
||||||
|
# Create setup tasks for base platforms first since everything will have
|
||||||
|
# to wait to be imported, and the sooner we can get the base platforms
|
||||||
|
# loaded the sooner we can start loading the rest of the integrations.
|
||||||
futures = {
|
futures = {
|
||||||
domain: hass.async_create_task(
|
domain: hass.async_create_task(
|
||||||
async_setup_component(hass, domain, config),
|
async_setup_component(hass, domain, config),
|
||||||
f"setup component {domain}",
|
f"setup component {domain}",
|
||||||
eager_start=True,
|
eager_start=True,
|
||||||
)
|
)
|
||||||
for domain in domains_not_yet_setup
|
for domain in sorted(
|
||||||
|
domains_not_yet_setup, key=SETUP_ORDER_SORT_KEY, reverse=True
|
||||||
|
)
|
||||||
}
|
}
|
||||||
results = await asyncio.gather(*futures.values(), return_exceptions=True)
|
results = await asyncio.gather(*futures.values(), return_exceptions=True)
|
||||||
for idx, domain in enumerate(futures):
|
for idx, domain in enumerate(futures):
|
||||||
@ -663,26 +728,53 @@ async def _async_resolve_domains_to_setup(
|
|||||||
hass: core.HomeAssistant, config: dict[str, Any]
|
hass: core.HomeAssistant, config: dict[str, Any]
|
||||||
) -> tuple[set[str], dict[str, loader.Integration]]:
|
) -> tuple[set[str], dict[str, loader.Integration]]:
|
||||||
"""Resolve all dependencies and return list of domains to set up."""
|
"""Resolve all dependencies and return list of domains to set up."""
|
||||||
base_platforms_loaded = False
|
|
||||||
domains_to_setup = _get_domains(hass, config)
|
domains_to_setup = _get_domains(hass, config)
|
||||||
needed_requirements: set[str] = set()
|
needed_requirements: set[str] = set()
|
||||||
|
platform_integrations = conf_util.extract_platform_integrations(
|
||||||
|
config, BASE_PLATFORMS
|
||||||
|
)
|
||||||
|
# Ensure base platforms that have platform integrations are added to
|
||||||
|
# to `domains_to_setup so they can be setup first instead of
|
||||||
|
# discovering them when later when a config entry setup task
|
||||||
|
# notices its needed and there is already a long line to use
|
||||||
|
# the import executor.
|
||||||
|
#
|
||||||
|
# For example if we have
|
||||||
|
# sensor:
|
||||||
|
# - platform: template
|
||||||
|
#
|
||||||
|
# `template` has to be loaded to validate the config for sensor
|
||||||
|
# so we want to start loading `sensor` as soon as we know
|
||||||
|
# it will be needed. The more platforms under `sensor:`, the longer
|
||||||
|
# it will take to finish setup for `sensor` because each of these
|
||||||
|
# platforms has to be imported before we can validate the config.
|
||||||
|
#
|
||||||
|
# Thankfully we are migrating away from the platform pattern
|
||||||
|
# so this will be less of a problem in the future.
|
||||||
|
domains_to_setup.update(platform_integrations)
|
||||||
|
|
||||||
|
# Load manifests for base platforms and platform based integrations
|
||||||
|
# that are defined under base platforms right away since we do not require
|
||||||
|
# the manifest to list them as dependencies and we want to avoid the lock
|
||||||
|
# contention when multiple integrations try to load them at once
|
||||||
|
additional_manifests_to_load = {
|
||||||
|
*BASE_PLATFORMS,
|
||||||
|
*chain.from_iterable(platform_integrations.values()),
|
||||||
|
}
|
||||||
|
|
||||||
|
translations_to_load = additional_manifests_to_load.copy()
|
||||||
|
|
||||||
# Resolve all dependencies so we know all integrations
|
# Resolve all dependencies so we know all integrations
|
||||||
# that will have to be loaded and start rightaway
|
# that will have to be loaded and start right-away
|
||||||
integration_cache: dict[str, loader.Integration] = {}
|
integration_cache: dict[str, loader.Integration] = {}
|
||||||
to_resolve: set[str] = domains_to_setup
|
to_resolve: set[str] = domains_to_setup
|
||||||
while to_resolve:
|
while to_resolve or additional_manifests_to_load:
|
||||||
old_to_resolve: set[str] = to_resolve
|
old_to_resolve: set[str] = to_resolve
|
||||||
to_resolve = set()
|
to_resolve = set()
|
||||||
|
|
||||||
if not base_platforms_loaded:
|
if additional_manifests_to_load:
|
||||||
# Load base platforms right away since
|
to_get = {*old_to_resolve, *additional_manifests_to_load}
|
||||||
# we do not require the manifest to list
|
additional_manifests_to_load.clear()
|
||||||
# them as dependencies and we want
|
|
||||||
# to avoid the lock contention when multiple
|
|
||||||
# integrations try to resolve them at once
|
|
||||||
base_platforms_loaded = True
|
|
||||||
to_get = {*old_to_resolve, *BASE_PLATFORMS}
|
|
||||||
else:
|
else:
|
||||||
to_get = old_to_resolve
|
to_get = old_to_resolve
|
||||||
|
|
||||||
@ -691,13 +783,27 @@ async def _async_resolve_domains_to_setup(
|
|||||||
integrations_to_process: list[loader.Integration] = []
|
integrations_to_process: list[loader.Integration] = []
|
||||||
|
|
||||||
for domain, itg in (await loader.async_get_integrations(hass, to_get)).items():
|
for domain, itg in (await loader.async_get_integrations(hass, to_get)).items():
|
||||||
if not isinstance(itg, loader.Integration) or domain not in old_to_resolve:
|
if not isinstance(itg, loader.Integration):
|
||||||
continue
|
continue
|
||||||
integrations_to_process.append(itg)
|
|
||||||
integration_cache[domain] = itg
|
integration_cache[domain] = itg
|
||||||
|
needed_requirements.update(itg.requirements)
|
||||||
|
|
||||||
|
# Make sure manifests for dependencies are loaded in the next
|
||||||
|
# loop to try to group as many as manifest loads in a single
|
||||||
|
# call to avoid the creating one-off executor jobs later in
|
||||||
|
# the setup process
|
||||||
|
additional_manifests_to_load.update(
|
||||||
|
dep
|
||||||
|
for dep in chain(itg.dependencies, itg.after_dependencies)
|
||||||
|
if dep not in integration_cache
|
||||||
|
)
|
||||||
|
|
||||||
|
if domain not in old_to_resolve:
|
||||||
|
continue
|
||||||
|
|
||||||
|
integrations_to_process.append(itg)
|
||||||
manifest_deps.update(itg.dependencies)
|
manifest_deps.update(itg.dependencies)
|
||||||
manifest_deps.update(itg.after_dependencies)
|
manifest_deps.update(itg.after_dependencies)
|
||||||
needed_requirements.update(itg.requirements)
|
|
||||||
if not itg.all_dependencies_resolved:
|
if not itg.all_dependencies_resolved:
|
||||||
resolve_dependencies_tasks.append(
|
resolve_dependencies_tasks.append(
|
||||||
create_eager_task(
|
create_eager_task(
|
||||||
@ -740,6 +846,12 @@ async def _async_resolve_domains_to_setup(
|
|||||||
"check installed requirements",
|
"check installed requirements",
|
||||||
eager_start=True,
|
eager_start=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Only add the domains_to_setup after we finish resolving
|
||||||
|
# as new domains are likely to added in the process
|
||||||
|
#
|
||||||
|
translations_to_load.update(domains_to_setup)
|
||||||
# Start loading translations for all integrations we are going to set up
|
# Start loading translations for all integrations we are going to set up
|
||||||
# in the background so they are ready when we need them. This avoids a
|
# in the background so they are ready when we need them. This avoids a
|
||||||
# lot of waiting for the translation load lock and a thundering herd of
|
# lot of waiting for the translation load lock and a thundering herd of
|
||||||
@ -751,11 +863,22 @@ async def _async_resolve_domains_to_setup(
|
|||||||
# wait for the translation load lock, loading will be done by the
|
# wait for the translation load lock, loading will be done by the
|
||||||
# time it gets to it.
|
# time it gets to it.
|
||||||
hass.async_create_background_task(
|
hass.async_create_background_task(
|
||||||
translation.async_load_integrations(hass, {*BASE_PLATFORMS, *domains_to_setup}),
|
translation.async_load_integrations(hass, translations_to_load),
|
||||||
"load translations",
|
"load translations",
|
||||||
eager_start=True,
|
eager_start=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Preload storage for all integrations we are going to set up
|
||||||
|
# so we do not have to wait for it to be loaded when we need it
|
||||||
|
# in the setup process.
|
||||||
|
hass.async_create_background_task(
|
||||||
|
get_internal_store_manager(hass).async_preload(
|
||||||
|
[*PRELOAD_STORAGE, *domains_to_setup]
|
||||||
|
),
|
||||||
|
"preload storage",
|
||||||
|
eager_start=True,
|
||||||
|
)
|
||||||
|
|
||||||
return domains_to_setup, integration_cache
|
return domains_to_setup, integration_cache
|
||||||
|
|
||||||
|
|
||||||
@ -763,10 +886,8 @@ async def _async_set_up_integrations(
|
|||||||
hass: core.HomeAssistant, config: dict[str, Any]
|
hass: core.HomeAssistant, config: dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up all the integrations."""
|
"""Set up all the integrations."""
|
||||||
setup_started: dict[str, float] = {}
|
setup_started: dict[tuple[str, str | None], float] = {}
|
||||||
hass.data[DATA_SETUP_STARTED] = setup_started
|
hass.data[DATA_SETUP_STARTED] = setup_started
|
||||||
setup_time: dict[str, timedelta] = hass.data.setdefault(DATA_SETUP_TIME, {})
|
|
||||||
|
|
||||||
watcher = _WatchPendingSetups(hass, setup_started)
|
watcher = _WatchPendingSetups(hass, setup_started)
|
||||||
watcher.async_start()
|
watcher.async_start()
|
||||||
|
|
||||||
@ -778,10 +899,9 @@ async def _async_set_up_integrations(
|
|||||||
if "recorder" in domains_to_setup:
|
if "recorder" in domains_to_setup:
|
||||||
recorder.async_initialize_recorder(hass)
|
recorder.async_initialize_recorder(hass)
|
||||||
|
|
||||||
pre_stage_domains: dict[str, set[str]] = {
|
pre_stage_domains = [
|
||||||
name: domains_to_setup & domain_group
|
(name, domains_to_setup & domain_group) for name, domain_group in SETUP_ORDER
|
||||||
for name, domain_group in SETUP_ORDER.items()
|
]
|
||||||
}
|
|
||||||
|
|
||||||
# calculate what components to setup in what stage
|
# calculate what components to setup in what stage
|
||||||
stage_1_domains: set[str] = set()
|
stage_1_domains: set[str] = set()
|
||||||
@ -807,10 +927,18 @@ async def _async_set_up_integrations(
|
|||||||
|
|
||||||
stage_2_domains = domains_to_setup - stage_1_domains
|
stage_2_domains = domains_to_setup - stage_1_domains
|
||||||
|
|
||||||
for name, domain_group in pre_stage_domains.items():
|
for name, domain_group in pre_stage_domains:
|
||||||
if domain_group:
|
if domain_group:
|
||||||
stage_2_domains -= domain_group
|
stage_2_domains -= domain_group
|
||||||
_LOGGER.info("Setting up %s: %s", name, domain_group)
|
_LOGGER.info("Setting up %s: %s", name, domain_group)
|
||||||
|
to_be_loaded = domain_group.copy()
|
||||||
|
to_be_loaded.update(
|
||||||
|
dep
|
||||||
|
for domain in domain_group
|
||||||
|
if (integration := integration_cache.get(domain)) is not None
|
||||||
|
for dep in integration.all_dependencies
|
||||||
|
)
|
||||||
|
async_set_domains_to_be_loaded(hass, to_be_loaded)
|
||||||
await async_setup_multi_components(hass, domain_group, config)
|
await async_setup_multi_components(hass, domain_group, config)
|
||||||
|
|
||||||
# Enables after dependencies when setting up stage 1 domains
|
# Enables after dependencies when setting up stage 1 domains
|
||||||
@ -825,7 +953,10 @@ async def _async_set_up_integrations(
|
|||||||
):
|
):
|
||||||
await async_setup_multi_components(hass, stage_1_domains, config)
|
await async_setup_multi_components(hass, stage_1_domains, config)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
_LOGGER.warning("Setup timed out for stage 1 - moving forward")
|
_LOGGER.warning(
|
||||||
|
"Setup timed out for stage 1 waiting on %s - moving forward",
|
||||||
|
hass._active_tasks, # pylint: disable=protected-access
|
||||||
|
)
|
||||||
|
|
||||||
# Add after dependencies when setting up stage 2 domains
|
# Add after dependencies when setting up stage 2 domains
|
||||||
async_set_domains_to_be_loaded(hass, stage_2_domains)
|
async_set_domains_to_be_loaded(hass, stage_2_domains)
|
||||||
@ -838,7 +969,10 @@ async def _async_set_up_integrations(
|
|||||||
):
|
):
|
||||||
await async_setup_multi_components(hass, stage_2_domains, config)
|
await async_setup_multi_components(hass, stage_2_domains, config)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
_LOGGER.warning("Setup timed out for stage 2 - moving forward")
|
_LOGGER.warning(
|
||||||
|
"Setup timed out for stage 2 waiting on %s - moving forward",
|
||||||
|
hass._active_tasks, # pylint: disable=protected-access
|
||||||
|
)
|
||||||
|
|
||||||
# Wrap up startup
|
# Wrap up startup
|
||||||
_LOGGER.debug("Waiting for startup to wrap up")
|
_LOGGER.debug("Waiting for startup to wrap up")
|
||||||
@ -846,11 +980,16 @@ async def _async_set_up_integrations(
|
|||||||
async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
|
async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
_LOGGER.warning("Setup timed out for bootstrap - moving forward")
|
_LOGGER.warning(
|
||||||
|
"Setup timed out for bootstrap waiting on %s - moving forward",
|
||||||
|
hass._active_tasks, # pylint: disable=protected-access
|
||||||
|
)
|
||||||
|
|
||||||
watcher.async_stop()
|
watcher.async_stop()
|
||||||
|
|
||||||
_LOGGER.debug(
|
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
"Integration setup times: %s",
|
setup_time = async_get_setup_timings(hass)
|
||||||
dict(sorted(setup_time.items(), key=itemgetter(1))),
|
_LOGGER.debug(
|
||||||
)
|
"Integration setup times: %s",
|
||||||
|
dict(sorted(setup_time.items(), key=itemgetter(1), reverse=True)),
|
||||||
|
)
|
||||||
|
5
homeassistant/brands/motionblinds.json
Normal file
5
homeassistant/brands/motionblinds.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"domain": "motionblinds",
|
||||||
|
"name": "Motionblinds",
|
||||||
|
"integrations": ["motion_blinds", "motionblinds_ble"]
|
||||||
|
}
|
@ -6,11 +6,13 @@ Component design guidelines:
|
|||||||
format "<DOMAIN>.<OBJECT_ID>".
|
format "<DOMAIN>.<OBJECT_ID>".
|
||||||
- Each component should publish services only under its own domain.
|
- Each component should publish services only under its own domain.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant, split_entity_id
|
from homeassistant.core import HomeAssistant, split_entity_id
|
||||||
|
from homeassistant.helpers.frame import report
|
||||||
from homeassistant.helpers.group import expand_entity_ids
|
from homeassistant.helpers.group import expand_entity_ids
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -21,6 +23,15 @@ def is_on(hass: HomeAssistant, entity_id: str | None = None) -> bool:
|
|||||||
|
|
||||||
If there is no entity id given we will check all.
|
If there is no entity id given we will check all.
|
||||||
"""
|
"""
|
||||||
|
report(
|
||||||
|
(
|
||||||
|
"uses homeassistant.components.is_on."
|
||||||
|
" This is deprecated and will stop working in Home Assistant 2024.9, it"
|
||||||
|
" should be updated to use the function of the platform directly."
|
||||||
|
),
|
||||||
|
error_if_core=True,
|
||||||
|
)
|
||||||
|
|
||||||
if entity_id:
|
if entity_id:
|
||||||
entity_ids = expand_entity_ids(hass, [entity_id])
|
entity_ids = expand_entity_ids(hass, [entity_id])
|
||||||
else:
|
else:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for the Abode Security System."""
|
"""Support for the Abode Security System."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
"""Support for Abode Security System alarm control panels."""
|
"""Support for Abode Security System alarm control panels."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from jaraco.abode.devices.alarm import Alarm as AbodeAl
|
from jaraco.abode.devices.alarm import Alarm
|
||||||
|
|
||||||
import homeassistant.components.alarm_control_panel as alarm
|
from homeassistant.components.alarm_control_panel import (
|
||||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
AlarmControlPanelEntity,
|
||||||
|
AlarmControlPanelEntityFeature,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ALARM_ARMED_AWAY,
|
STATE_ALARM_ARMED_AWAY,
|
||||||
@ -28,7 +31,7 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
class AbodeAlarm(AbodeDevice, AlarmControlPanelEntity):
|
||||||
"""An alarm_control_panel implementation for Abode."""
|
"""An alarm_control_panel implementation for Abode."""
|
||||||
|
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
@ -37,7 +40,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
|||||||
AlarmControlPanelEntityFeature.ARM_HOME
|
AlarmControlPanelEntityFeature.ARM_HOME
|
||||||
| AlarmControlPanelEntityFeature.ARM_AWAY
|
| AlarmControlPanelEntityFeature.ARM_AWAY
|
||||||
)
|
)
|
||||||
_device: AbodeAl
|
_device: Alarm
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> str | None:
|
def state(self) -> str | None:
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
"""Support for Abode Security System binary sensors."""
|
"""Support for Abode Security System binary sensors."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from jaraco.abode.devices.sensor import BinarySensor as ABBinarySensor
|
from jaraco.abode.devices.sensor import BinarySensor
|
||||||
from jaraco.abode.helpers import constants as CONST
|
from jaraco.abode.helpers.constants import (
|
||||||
|
TYPE_CONNECTIVITY,
|
||||||
|
TYPE_MOISTURE,
|
||||||
|
TYPE_MOTION,
|
||||||
|
TYPE_OCCUPANCY,
|
||||||
|
TYPE_OPENING,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
BinarySensorDeviceClass,
|
BinarySensorDeviceClass,
|
||||||
@ -26,11 +33,11 @@ async def async_setup_entry(
|
|||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
|
||||||
device_types = [
|
device_types = [
|
||||||
CONST.TYPE_CONNECTIVITY,
|
TYPE_CONNECTIVITY,
|
||||||
CONST.TYPE_MOISTURE,
|
TYPE_MOISTURE,
|
||||||
CONST.TYPE_MOTION,
|
TYPE_MOTION,
|
||||||
CONST.TYPE_OCCUPANCY,
|
TYPE_OCCUPANCY,
|
||||||
CONST.TYPE_OPENING,
|
TYPE_OPENING,
|
||||||
]
|
]
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
@ -43,7 +50,7 @@ class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
|
|||||||
"""A binary sensor implementation for Abode device."""
|
"""A binary sensor implementation for Abode device."""
|
||||||
|
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
_device: ABBinarySensor
|
_device: BinarySensor
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
"""Support for Abode Security System cameras."""
|
"""Support for Abode Security System cameras."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from jaraco.abode.devices.base import Device as AbodeDev
|
from jaraco.abode.devices.base import Device
|
||||||
from jaraco.abode.devices.camera import Camera as AbodeCam
|
from jaraco.abode.devices.camera import Camera as AbodeCam
|
||||||
from jaraco.abode.helpers import constants as CONST, timeline as TIMELINE
|
from jaraco.abode.helpers import timeline
|
||||||
|
from jaraco.abode.helpers.constants import TYPE_CAMERA
|
||||||
import requests
|
import requests
|
||||||
from requests.models import Response
|
from requests.models import Response
|
||||||
|
|
||||||
@ -30,8 +32,8 @@ async def async_setup_entry(
|
|||||||
data: AbodeSystem = hass.data[DOMAIN]
|
data: AbodeSystem = hass.data[DOMAIN]
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE)
|
AbodeCamera(data, device, timeline.CAPTURE_IMAGE)
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA)
|
for device in data.abode.get_devices(generic_type=TYPE_CAMERA)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ class AbodeCamera(AbodeDevice, Camera):
|
|||||||
_device: AbodeCam
|
_device: AbodeCam
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
|
def __init__(self, data: AbodeSystem, device: Device, event: Event) -> None:
|
||||||
"""Initialize the Abode device."""
|
"""Initialize the Abode device."""
|
||||||
AbodeDevice.__init__(self, data, device)
|
AbodeDevice.__init__(self, data, device)
|
||||||
Camera.__init__(self)
|
Camera.__init__(self)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Config flow for the Abode Security System component."""
|
"""Config flow for the Abode Security System component."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
@ -14,16 +15,15 @@ from jaraco.abode.helpers.errors import MFA_CODE_REQUIRED
|
|||||||
from requests.exceptions import ConnectTimeout, HTTPError
|
from requests.exceptions import ConnectTimeout, HTTPError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
|
|
||||||
from .const import CONF_POLLING, DOMAIN, LOGGER
|
from .const import CONF_POLLING, DOMAIN, LOGGER
|
||||||
|
|
||||||
CONF_MFA = "mfa_code"
|
CONF_MFA = "mfa_code"
|
||||||
|
|
||||||
|
|
||||||
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class AbodeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
"""Config flow for Abode."""
|
"""Config flow for Abode."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
@ -43,7 +43,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._polling: bool = False
|
self._polling: bool = False
|
||||||
self._username: str | None = None
|
self._username: str | None = None
|
||||||
|
|
||||||
async def _async_abode_login(self, step_id: str) -> FlowResult:
|
async def _async_abode_login(self, step_id: str) -> ConfigFlowResult:
|
||||||
"""Handle login with Abode."""
|
"""Handle login with Abode."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
return await self._async_create_entry()
|
return await self._async_create_entry()
|
||||||
|
|
||||||
async def _async_abode_mfa_login(self) -> FlowResult:
|
async def _async_abode_mfa_login(self) -> ConfigFlowResult:
|
||||||
"""Handle multi-factor authentication (MFA) login with Abode."""
|
"""Handle multi-factor authentication (MFA) login with Abode."""
|
||||||
try:
|
try:
|
||||||
# Create instance to access login method for passing MFA code
|
# Create instance to access login method for passing MFA code
|
||||||
@ -92,7 +92,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
return await self._async_create_entry()
|
return await self._async_create_entry()
|
||||||
|
|
||||||
async def _async_create_entry(self) -> FlowResult:
|
async def _async_create_entry(self) -> ConfigFlowResult:
|
||||||
"""Create the config entry."""
|
"""Create the config entry."""
|
||||||
config_data = {
|
config_data = {
|
||||||
CONF_USERNAME: self._username,
|
CONF_USERNAME: self._username,
|
||||||
@ -118,7 +118,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
if self._async_current_entries():
|
if self._async_current_entries():
|
||||||
return self.async_abort(reason="single_instance_allowed")
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
@ -135,7 +135,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_mfa(
|
async def async_step_mfa(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle a multi-factor authentication (MFA) flow."""
|
"""Handle a multi-factor authentication (MFA) flow."""
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
@ -146,7 +146,9 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
return await self._async_abode_mfa_login()
|
return await self._async_abode_mfa_login()
|
||||||
|
|
||||||
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
async def async_step_reauth(
|
||||||
|
self, entry_data: Mapping[str, Any]
|
||||||
|
) -> ConfigFlowResult:
|
||||||
"""Handle reauthorization request from Abode."""
|
"""Handle reauthorization request from Abode."""
|
||||||
self._username = entry_data[CONF_USERNAME]
|
self._username = entry_data[CONF_USERNAME]
|
||||||
|
|
||||||
@ -154,7 +156,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_reauth_confirm(
|
async def async_step_reauth_confirm(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle reauthorization flow."""
|
"""Handle reauthorization flow."""
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Constants for the Abode Security System component."""
|
"""Constants for the Abode Security System component."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
"""Support for Abode Security System covers."""
|
"""Support for Abode Security System covers."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from jaraco.abode.devices.cover import Cover as AbodeCV
|
from jaraco.abode.devices.cover import Cover
|
||||||
from jaraco.abode.helpers import constants as CONST
|
from jaraco.abode.helpers.constants import TYPE_COVER
|
||||||
|
|
||||||
from homeassistant.components.cover import CoverEntity
|
from homeassistant.components.cover import CoverEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -21,14 +22,14 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AbodeCover(data, device)
|
AbodeCover(data, device)
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_COVER)
|
for device in data.abode.get_devices(generic_type=TYPE_COVER)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AbodeCover(AbodeDevice, CoverEntity):
|
class AbodeCover(AbodeDevice, CoverEntity):
|
||||||
"""Representation of an Abode cover."""
|
"""Representation of an Abode cover."""
|
||||||
|
|
||||||
_device: AbodeCV
|
_device: Cover
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -5,5 +5,10 @@
|
|||||||
"default": "mdi:robot"
|
"default": "mdi:robot"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"capture_image": "mdi:camera",
|
||||||
|
"change_setting": "mdi:cog",
|
||||||
|
"trigger_automation": "mdi:play"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
"""Support for Abode Security System lights."""
|
"""Support for Abode Security System lights."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from jaraco.abode.devices.light import Light as AbodeLT
|
from jaraco.abode.devices.light import Light
|
||||||
from jaraco.abode.helpers import constants as CONST
|
from jaraco.abode.helpers.constants import TYPE_LIGHT
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
@ -34,14 +35,14 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AbodeLight(data, device)
|
AbodeLight(data, device)
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_LIGHT)
|
for device in data.abode.get_devices(generic_type=TYPE_LIGHT)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AbodeLight(AbodeDevice, LightEntity):
|
class AbodeLight(AbodeDevice, LightEntity):
|
||||||
"""Representation of an Abode light."""
|
"""Representation of an Abode light."""
|
||||||
|
|
||||||
_device: AbodeLT
|
_device: Light
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
def turn_on(self, **kwargs: Any) -> None:
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
"""Support for the Abode Security System locks."""
|
"""Support for the Abode Security System locks."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from jaraco.abode.devices.lock import Lock as AbodeLK
|
from jaraco.abode.devices.lock import Lock
|
||||||
from jaraco.abode.helpers import constants as CONST
|
from jaraco.abode.helpers.constants import TYPE_LOCK
|
||||||
|
|
||||||
from homeassistant.components.lock import LockEntity
|
from homeassistant.components.lock import LockEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -21,14 +22,14 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AbodeLock(data, device)
|
AbodeLock(data, device)
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_LOCK)
|
for device in data.abode.get_devices(generic_type=TYPE_LOCK)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AbodeLock(AbodeDevice, LockEntity):
|
class AbodeLock(AbodeDevice, LockEntity):
|
||||||
"""Representation of an Abode lock."""
|
"""Representation of an Abode lock."""
|
||||||
|
|
||||||
_device: AbodeLK
|
_device: Lock
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
|
||||||
def lock(self, **kwargs: Any) -> None:
|
def lock(self, **kwargs: Any) -> None:
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
"""Support for Abode Security System sensors."""
|
"""Support for Abode Security System sensors."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from jaraco.abode.devices.sensor import Sensor as AbodeSense
|
from jaraco.abode.devices.sensor import Sensor
|
||||||
from jaraco.abode.helpers import constants as CONST
|
from jaraco.abode.helpers.constants import (
|
||||||
|
HUMI_STATUS_KEY,
|
||||||
|
LUX_STATUS_KEY,
|
||||||
|
STATUSES_KEY,
|
||||||
|
TEMP_STATUS_KEY,
|
||||||
|
TYPE_SENSOR,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_FAHRENHEIT,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -22,27 +31,22 @@ from . import AbodeDevice, AbodeSystem
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
ABODE_TEMPERATURE_UNIT_HA_UNIT = {
|
ABODE_TEMPERATURE_UNIT_HA_UNIT = {
|
||||||
CONST.UNIT_FAHRENHEIT: UnitOfTemperature.FAHRENHEIT,
|
UNIT_FAHRENHEIT: UnitOfTemperature.FAHRENHEIT,
|
||||||
CONST.UNIT_CELSIUS: UnitOfTemperature.CELSIUS,
|
UNIT_CELSIUS: UnitOfTemperature.CELSIUS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class AbodeSensorDescriptionMixin:
|
class AbodeSensorDescription(SensorEntityDescription):
|
||||||
"""Mixin for Abode sensor."""
|
|
||||||
|
|
||||||
value_fn: Callable[[AbodeSense], float]
|
|
||||||
native_unit_of_measurement_fn: Callable[[AbodeSense], str]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AbodeSensorDescription(SensorEntityDescription, AbodeSensorDescriptionMixin):
|
|
||||||
"""Class describing Abode sensor entities."""
|
"""Class describing Abode sensor entities."""
|
||||||
|
|
||||||
|
value_fn: Callable[[Sensor], float]
|
||||||
|
native_unit_of_measurement_fn: Callable[[Sensor], str]
|
||||||
|
|
||||||
|
|
||||||
SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = (
|
SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = (
|
||||||
AbodeSensorDescription(
|
AbodeSensorDescription(
|
||||||
key=CONST.TEMP_STATUS_KEY,
|
key=TEMP_STATUS_KEY,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
native_unit_of_measurement_fn=lambda device: ABODE_TEMPERATURE_UNIT_HA_UNIT[
|
native_unit_of_measurement_fn=lambda device: ABODE_TEMPERATURE_UNIT_HA_UNIT[
|
||||||
device.temp_unit
|
device.temp_unit
|
||||||
@ -50,13 +54,13 @@ SENSOR_TYPES: tuple[AbodeSensorDescription, ...] = (
|
|||||||
value_fn=lambda device: cast(float, device.temp),
|
value_fn=lambda device: cast(float, device.temp),
|
||||||
),
|
),
|
||||||
AbodeSensorDescription(
|
AbodeSensorDescription(
|
||||||
key=CONST.HUMI_STATUS_KEY,
|
key=HUMI_STATUS_KEY,
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
native_unit_of_measurement_fn=lambda _: PERCENTAGE,
|
native_unit_of_measurement_fn=lambda _: PERCENTAGE,
|
||||||
value_fn=lambda device: cast(float, device.humidity),
|
value_fn=lambda device: cast(float, device.humidity),
|
||||||
),
|
),
|
||||||
AbodeSensorDescription(
|
AbodeSensorDescription(
|
||||||
key=CONST.LUX_STATUS_KEY,
|
key=LUX_STATUS_KEY,
|
||||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||||
native_unit_of_measurement_fn=lambda _: LIGHT_LUX,
|
native_unit_of_measurement_fn=lambda _: LIGHT_LUX,
|
||||||
value_fn=lambda device: cast(float, device.lux),
|
value_fn=lambda device: cast(float, device.lux),
|
||||||
@ -73,8 +77,8 @@ async def async_setup_entry(
|
|||||||
async_add_entities(
|
async_add_entities(
|
||||||
AbodeSensor(data, device, description)
|
AbodeSensor(data, device, description)
|
||||||
for description in SENSOR_TYPES
|
for description in SENSOR_TYPES
|
||||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR)
|
for device in data.abode.get_devices(generic_type=TYPE_SENSOR)
|
||||||
if description.key in device.get_value(CONST.STATUSES_KEY)
|
if description.key in device.get_value(STATUSES_KEY)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -82,12 +86,12 @@ class AbodeSensor(AbodeDevice, SensorEntity):
|
|||||||
"""A sensor implementation for Abode devices."""
|
"""A sensor implementation for Abode devices."""
|
||||||
|
|
||||||
entity_description: AbodeSensorDescription
|
entity_description: AbodeSensorDescription
|
||||||
_device: AbodeSense
|
_device: Sensor
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
data: AbodeSystem,
|
data: AbodeSystem,
|
||||||
device: AbodeSense,
|
device: Sensor,
|
||||||
description: AbodeSensorDescription,
|
description: AbodeSensorDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a sensor for an Abode device."""
|
"""Initialize a sensor for an Abode device."""
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"""Support for Abode Security System switches."""
|
"""Support for Abode Security System switches."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from jaraco.abode.devices.switch import Switch as AbodeSW
|
from jaraco.abode.devices.switch import Switch
|
||||||
from jaraco.abode.helpers import constants as CONST
|
from jaraco.abode.helpers.constants import TYPE_SWITCH, TYPE_VALVE
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -15,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from . import AbodeAutomation, AbodeDevice, AbodeSystem
|
from . import AbodeAutomation, AbodeDevice, AbodeSystem
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE]
|
DEVICE_TYPES = [TYPE_SWITCH, TYPE_VALVE]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -41,7 +42,7 @@ async def async_setup_entry(
|
|||||||
class AbodeSwitch(AbodeDevice, SwitchEntity):
|
class AbodeSwitch(AbodeDevice, SwitchEntity):
|
||||||
"""Representation of an Abode switch."""
|
"""Representation of an Abode switch."""
|
||||||
|
|
||||||
_device: AbodeSW
|
_device: Switch
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
def turn_on(self, **kwargs: Any) -> None:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""The AccuWeather component."""
|
"""The AccuWeather component."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from asyncio import timeout
|
from asyncio import timeout
|
||||||
@ -51,7 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
# Remove ozone sensors from registry if they exist
|
# Remove ozone sensors from registry if they exist
|
||||||
ent_reg = er.async_get(hass)
|
ent_reg = er.async_get(hass)
|
||||||
for day in range(0, 5):
|
for day in range(5):
|
||||||
unique_id = f"{coordinator.location_key}-ozone-{day}"
|
unique_id = f"{coordinator.location_key}-ozone-{day}"
|
||||||
if entity_id := ent_reg.async_get_entity_id(SENSOR_PLATFORM, DOMAIN, unique_id):
|
if entity_id := ent_reg.async_get_entity_id(SENSOR_PLATFORM, DOMAIN, unique_id):
|
||||||
_LOGGER.debug("Removing ozone sensor entity %s", entity_id)
|
_LOGGER.debug("Removing ozone sensor entity %s", entity_id)
|
||||||
@ -134,4 +135,4 @@ class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
) as error:
|
) as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(error) from error
|
||||||
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
|
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
|
||||||
return {**current, **{ATTR_FORECAST: forecast}}
|
return {**current, ATTR_FORECAST: forecast}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Adds config flow for AccuWeather."""
|
"""Adds config flow for AccuWeather."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from asyncio import timeout
|
from asyncio import timeout
|
||||||
@ -9,11 +10,9 @@ from aiohttp import ClientError
|
|||||||
from aiohttp.client_exceptions import ClientConnectorError
|
from aiohttp.client_exceptions import ClientConnectorError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.schema_config_entry_flow import (
|
from homeassistant.helpers.schema_config_entry_flow import (
|
||||||
@ -33,20 +32,15 @@ OPTIONS_FLOW = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AccuWeatherFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
"""Config flow for AccuWeather."""
|
"""Config flow for AccuWeather."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
# Under the terms of use of the API, one user can use one free API key. Due to
|
|
||||||
# the small number of requests allowed, we only allow one integration instance.
|
|
||||||
if self._async_current_entries():
|
|
||||||
return self.async_abort(reason="single_instance_allowed")
|
|
||||||
|
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Constants for AccuWeather integration."""
|
"""Constants for AccuWeather integration."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Diagnostics support for AccuWeather."""
|
"""Diagnostics support for AccuWeather."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -8,5 +8,6 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["accuweather"],
|
"loggers": ["accuweather"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["accuweather==2.1.1"]
|
"requirements": ["accuweather==2.1.1"],
|
||||||
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for the AccuWeather service."""
|
"""Support for the AccuWeather service."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
@ -45,19 +46,11 @@ from .const import (
|
|||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class AccuWeatherSensorDescriptionMixin:
|
class AccuWeatherSensorDescription(SensorEntityDescription):
|
||||||
"""Mixin for AccuWeather sensor."""
|
|
||||||
|
|
||||||
value_fn: Callable[[dict[str, Any]], str | int | float | None]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AccuWeatherSensorDescription(
|
|
||||||
SensorEntityDescription, AccuWeatherSensorDescriptionMixin
|
|
||||||
):
|
|
||||||
"""Class describing AccuWeather sensor entities."""
|
"""Class describing AccuWeather sensor entities."""
|
||||||
|
|
||||||
|
value_fn: Callable[[dict[str, Any]], str | int | float | None]
|
||||||
attr_fn: Callable[[dict[str, Any]], dict[str, Any]] = lambda _: {}
|
attr_fn: Callable[[dict[str, Any]], dict[str, Any]] = lambda _: {}
|
||||||
day: int | None = None
|
day: int | None = None
|
||||||
|
|
||||||
|
@ -17,9 +17,6 @@
|
|||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
|
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
|
||||||
"requests_exceeded": "The allowed number of requests to Accuweather API has been exceeded. You have to wait or change API Key."
|
"requests_exceeded": "The allowed number of requests to Accuweather API has been exceeded. You have to wait or change API Key."
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Provide info to system health."""
|
"""Provide info to system health."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for the AccuWeather service."""
|
"""Support for the AccuWeather service."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
@ -145,9 +146,9 @@ class AccuWeatherEntity(
|
|||||||
"""Return the UV index."""
|
"""Return the UV index."""
|
||||||
return cast(float, self.coordinator.data["UVIndex"])
|
return cast(float, self.coordinator.data["UVIndex"])
|
||||||
|
|
||||||
@property
|
@callback
|
||||||
def forecast(self) -> list[Forecast] | None:
|
def _async_forecast_daily(self) -> list[Forecast] | None:
|
||||||
"""Return the forecast array."""
|
"""Return the daily forecast in native units."""
|
||||||
if not self.coordinator.forecast:
|
if not self.coordinator.forecast:
|
||||||
return None
|
return None
|
||||||
# remap keys from library to keys understood by the weather component
|
# remap keys from library to keys understood by the weather component
|
||||||
@ -176,8 +177,3 @@ class AccuWeatherEntity(
|
|||||||
}
|
}
|
||||||
for item in self.coordinator.data[ATTR_FORECAST]
|
for item in self.coordinator.data[ATTR_FORECAST]
|
||||||
]
|
]
|
||||||
|
|
||||||
@callback
|
|
||||||
def _async_forecast_daily(self) -> list[Forecast] | None:
|
|
||||||
"""Return the daily forecast in native units."""
|
|
||||||
return self.forecast
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
"""Use serial protocol of Acer projector to obtain state of the projector."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""The Rollease Acmeda Automate integration."""
|
"""The Rollease Acmeda Automate integration."""
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Base class for Acmeda Roller Blinds."""
|
"""Base class for Acmeda Roller Blinds."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import aiopulse
|
import aiopulse
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Config flow for Rollease Acmeda Automate Pulse Hub."""
|
"""Config flow for Rollease Acmeda Automate Pulse Hub."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from asyncio import timeout
|
from asyncio import timeout
|
||||||
@ -8,14 +9,13 @@ from typing import Any
|
|||||||
import aiopulse
|
import aiopulse
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_HOST, CONF_ID
|
from homeassistant.const import CONF_HOST, CONF_ID
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class AcmedaFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a Acmeda config flow."""
|
"""Handle a Acmeda config flow."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
@ -26,7 +26,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
if (
|
if (
|
||||||
user_input is not None
|
user_input is not None
|
||||||
@ -40,12 +40,13 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
entry.unique_id for entry in self._async_current_entries()
|
entry.unique_id for entry in self._async_current_entries()
|
||||||
}
|
}
|
||||||
|
|
||||||
hubs: list[aiopulse.Hub] = []
|
|
||||||
with suppress(TimeoutError):
|
with suppress(TimeoutError):
|
||||||
async with timeout(5):
|
async with timeout(5):
|
||||||
async for hub in aiopulse.Hub.discover():
|
hubs: list[aiopulse.Hub] = [
|
||||||
if hub.id not in already_configured:
|
hub
|
||||||
hubs.append(hub)
|
async for hub in aiopulse.Hub.discover()
|
||||||
|
if hub.id not in already_configured
|
||||||
|
]
|
||||||
|
|
||||||
if not hubs:
|
if not hubs:
|
||||||
return self.async_abort(reason="no_devices_found")
|
return self.async_abort(reason="no_devices_found")
|
||||||
@ -66,7 +67,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_create(self, hub: aiopulse.Hub) -> FlowResult:
|
async def async_create(self, hub: aiopulse.Hub) -> ConfigFlowResult:
|
||||||
"""Create the Acmeda Hub entry."""
|
"""Create the Acmeda Hub entry."""
|
||||||
await self.async_set_unique_id(hub.id, raise_on_progress=False)
|
await self.async_set_unique_id(hub.id, raise_on_progress=False)
|
||||||
return self.async_create_entry(title=hub.id, data={CONF_HOST: hub.host})
|
return self.async_create_entry(title=hub.id, data={CONF_HOST: hub.host})
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Constants for the Rollease Acmeda Automate integration."""
|
"""Constants for the Rollease Acmeda Automate integration."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for Acmeda Roller Blinds."""
|
"""Support for Acmeda Roller Blinds."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Errors for the Acmeda Pulse component."""
|
"""Errors for the Acmeda Pulse component."""
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Helper functions for Acmeda Pulse."""
|
"""Helper functions for Acmeda Pulse."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from aiopulse import Roller
|
from aiopulse import Roller
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Code to handle a Pulse Hub."""
|
"""Code to handle a Pulse Hub."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for Acmeda Roller Blind Batteries."""
|
"""Support for Acmeda Roller Blind Batteries."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for Actiontec MI424WR (Verizon FIOS) routers."""
|
"""Support for Actiontec MI424WR (Verizon FIOS) routers."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@ -8,7 +9,7 @@ from typing import Final
|
|||||||
|
|
||||||
LEASES_REGEX: Final[re.Pattern[str]] = re.compile(
|
LEASES_REGEX: Final[re.Pattern[str]] = re.compile(
|
||||||
r"(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})"
|
r"(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})"
|
||||||
+ r"\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))"
|
r"\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))"
|
||||||
+ r"\svalid\sfor:\s(?P<timevalid>(-?\d+))"
|
r"\svalid\sfor:\s(?P<timevalid>(-?\d+))"
|
||||||
+ r"\ssec"
|
r"\ssec"
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for Actiontec MI424WR (Verizon FIOS) routers."""
|
"""Support for Actiontec MI424WR (Verizon FIOS) routers."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Model definitions for Actiontec MI424WR (Verizon FIOS) routers."""
|
"""Model definitions for Actiontec MI424WR (Verizon FIOS) routers."""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""The Adax integration."""
|
"""The Adax integration."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for Adax wifi-enabled home heaters."""
|
"""Support for Adax wifi-enabled home heaters."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Config flow for Adax integration."""
|
"""Config flow for Adax integration."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -8,14 +9,13 @@ import adax
|
|||||||
import adax_local
|
import adax_local
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_IP_ADDRESS,
|
CONF_IP_ADDRESS,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_TOKEN,
|
CONF_TOKEN,
|
||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
)
|
)
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -31,14 +31,14 @@ from .const import (
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class AdaxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Adax."""
|
"""Handle a config flow for Adax."""
|
||||||
|
|
||||||
VERSION = 2
|
VERSION = 2
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
data_schema = vol.Schema(
|
data_schema = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -63,7 +63,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_local(
|
async def async_step_local(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle the local step."""
|
"""Handle the local step."""
|
||||||
data_schema = vol.Schema(
|
data_schema = vol.Schema(
|
||||||
{vol.Required(WIFI_SSID): str, vol.Required(WIFI_PSWD): str}
|
{vol.Required(WIFI_SSID): str, vol.Required(WIFI_PSWD): str}
|
||||||
@ -110,7 +110,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_cloud(
|
async def async_step_cloud(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle the cloud step."""
|
"""Handle the cloud step."""
|
||||||
data_schema = vol.Schema(
|
data_schema = vol.Schema(
|
||||||
{vol.Required(ACCOUNT_ID): int, vol.Required(CONF_PASSWORD): str}
|
{vol.Required(ACCOUNT_ID): int, vol.Required(CONF_PASSWORD): str}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Constants for the Adax integration."""
|
"""Constants for the Adax integration."""
|
||||||
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
ACCOUNT_ID: Final = "account_id"
|
ACCOUNT_ID: Final = "account_id"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for AdGuard Home."""
|
"""Support for AdGuard Home."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Config flow to configure the AdGuard Home integration."""
|
"""Config flow to configure the AdGuard Home integration."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -7,7 +8,7 @@ from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.hassio import HassioServiceInfo
|
from homeassistant.components.hassio import HassioServiceInfo
|
||||||
from homeassistant.config_entries import ConfigFlow
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
@ -16,7 +17,6 @@ from homeassistant.const import (
|
|||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
CONF_VERIFY_SSL,
|
CONF_VERIFY_SSL,
|
||||||
)
|
)
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -31,7 +31,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def _show_setup_form(
|
async def _show_setup_form(
|
||||||
self, errors: dict[str, str] | None = None
|
self, errors: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Show the setup form to the user."""
|
"""Show the setup form to the user."""
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
@ -50,7 +50,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def _show_hassio_form(
|
async def _show_hassio_form(
|
||||||
self, errors: dict[str, str] | None = None
|
self, errors: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Show the Hass.io confirmation form to the user."""
|
"""Show the Hass.io confirmation form to the user."""
|
||||||
assert self._hassio_discovery
|
assert self._hassio_discovery
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
@ -61,7 +61,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle a flow initiated by the user."""
|
"""Handle a flow initiated by the user."""
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return await self._show_setup_form(user_input)
|
return await self._show_setup_form(user_input)
|
||||||
@ -104,7 +104,9 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult:
|
async def async_step_hassio(
|
||||||
|
self, discovery_info: HassioServiceInfo
|
||||||
|
) -> ConfigFlowResult:
|
||||||
"""Prepare configuration for a Hass.io AdGuard Home add-on.
|
"""Prepare configuration for a Hass.io AdGuard Home add-on.
|
||||||
|
|
||||||
This flow is triggered by the discovery component.
|
This flow is triggered by the discovery component.
|
||||||
@ -116,7 +118,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_hassio_confirm(
|
async def async_step_hassio_confirm(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Confirm Supervisor discovery."""
|
"""Confirm Supervisor discovery."""
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return await self._show_hassio_form()
|
return await self._show_hassio_form()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Constants for the AdGuard Home integration."""
|
"""Constants for the AdGuard Home integration."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
DOMAIN = "adguard"
|
DOMAIN = "adguard"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""AdGuard Home base entity."""
|
"""AdGuard Home base entity."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from adguardhome import AdGuardHome, AdGuardHomeError
|
from adguardhome import AdGuardHome, AdGuardHomeError
|
||||||
@ -43,7 +44,7 @@ class AdGuardHomeEntity(Entity):
|
|||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
async def _adguard_update(self) -> None:
|
||||||
"""Update AdGuard Home entity."""
|
"""Update AdGuard Home entity."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for AdGuard Home sensors."""
|
"""Support for AdGuard Home sensors."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for AdGuard Home switches."""
|
"""Support for AdGuard Home switches."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for Automation Device Specification (ADS)."""
|
"""Support for Automation Device Specification (ADS)."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from asyncio import timeout
|
from asyncio import timeout
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for ADS binary sensors."""
|
"""Support for ADS binary sensors."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pyads
|
import pyads
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for ADS covers."""
|
"""Support for ADS covers."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
5
homeassistant/components/ads/icons.json
Normal file
5
homeassistant/components/ads/icons.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"services": {
|
||||||
|
"write_data_by_name": "mdi:pencil"
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for ADS light sources."""
|
"""Support for ADS light sources."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for ADS sensors."""
|
"""Support for ADS sensors."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for ADS switch platform."""
|
"""Support for ADS switch platform."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Advantage Air climate integration."""
|
"""Advantage Air climate integration."""
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user