Compare commits

..

2 Commits

Author SHA1 Message Date
feedc0de 57263bb65a goe_charger cleanups and improvements 2021-09-15 21:25:23 +02:00
feedc0de 86533e3599 Added goe_charger 2021-09-13 12:27:15 +02:00
23868 changed files with 405293 additions and 1253510 deletions
-145
View File
@@ -1,145 +0,0 @@
# Defines a list of files that are part of main core of Home Assistant.
# Changes to these files/filters define how our CI test suite is ran.
core: &core
- homeassistant/*.py
- homeassistant/auth/**
- homeassistant/helpers/**
- homeassistant/package_constraints.txt
- homeassistant/util/**
- pyproject.toml
- requirements.txt
- setup.cfg
# Our base platforms, that are used by other integrations
base_platforms: &base_platforms
- homeassistant/components/air_quality/**
- homeassistant/components/alarm_control_panel/**
- homeassistant/components/binary_sensor/**
- homeassistant/components/button/**
- homeassistant/components/calendar/**
- homeassistant/components/camera/**
- homeassistant/components/climate/**
- homeassistant/components/cover/**
- homeassistant/components/device_tracker/**
- homeassistant/components/diagnostics/**
- homeassistant/components/fan/**
- homeassistant/components/geo_location/**
- homeassistant/components/humidifier/**
- homeassistant/components/image_processing/**
- homeassistant/components/light/**
- homeassistant/components/lock/**
- homeassistant/components/media_player/**
- homeassistant/components/notify/**
- homeassistant/components/number/**
- homeassistant/components/remote/**
- homeassistant/components/scene/**
- homeassistant/components/select/**
- homeassistant/components/sensor/**
- homeassistant/components/siren/**
- homeassistant/components/stt/**
- homeassistant/components/switch/**
- homeassistant/components/text/**
- homeassistant/components/tts/**
- homeassistant/components/update/**
- homeassistant/components/vacuum/**
- homeassistant/components/water_heater/**
- homeassistant/components/weather/**
# Extra components that trigger the full suite
components: &components
- homeassistant/components/alexa/**
- homeassistant/components/application_credentials/**
- homeassistant/components/auth/**
- homeassistant/components/automation/**
- homeassistant/components/backup/**
- homeassistant/components/bluetooth/**
- homeassistant/components/cloud/**
- homeassistant/components/config/**
- homeassistant/components/configurator/**
- homeassistant/components/conversation/**
- homeassistant/components/demo/**
- homeassistant/components/device_automation/**
- homeassistant/components/dhcp/**
- homeassistant/components/discovery/**
- homeassistant/components/energy/**
- homeassistant/components/ffmpeg/**
- homeassistant/components/frontend/**
- homeassistant/components/google_assistant/**
- homeassistant/components/group/**
- homeassistant/components/hassio/**
- homeassistant/components/homeassistant/**
- homeassistant/components/http/**
- homeassistant/components/image/**
- homeassistant/components/input_boolean/**
- homeassistant/components/input_button/**
- homeassistant/components/input_datetime/**
- homeassistant/components/input_number/**
- homeassistant/components/input_select/**
- homeassistant/components/input_text/**
- homeassistant/components/logbook/**
- homeassistant/components/logger/**
- homeassistant/components/lovelace/**
- homeassistant/components/media_source/**
- homeassistant/components/mjpeg/**
- homeassistant/components/mqtt/**
- homeassistant/components/network/**
- homeassistant/components/onboarding/**
- homeassistant/components/otp/**
- homeassistant/components/persistent_notification/**
- homeassistant/components/person/**
- homeassistant/components/recorder/**
- homeassistant/components/repairs/**
- homeassistant/components/safe_mode/**
- homeassistant/components/script/**
- homeassistant/components/shopping_list/**
- homeassistant/components/ssdp/**
- homeassistant/components/stream/**
- homeassistant/components/sun/**
- homeassistant/components/system_health/**
- homeassistant/components/tag/**
- homeassistant/components/template/**
- homeassistant/components/timer/**
- homeassistant/components/usb/**
- homeassistant/components/webhook/**
- homeassistant/components/websocket_api/**
- homeassistant/components/zeroconf/**
- homeassistant/components/zone/**
# Testing related files that affect the whole test/linting suite
tests: &tests
- codecov.yaml
- pylint/**
- requirements_test_pre_commit.txt
- requirements_test.txt
- tests/auth/**
- tests/backports/**
- tests/common.py
- tests/conftest.py
- tests/hassfest/**
- tests/helpers/**
- tests/ignore_uncaught_exceptions.py
- tests/mock/**
- tests/pylint/**
- tests/scripts/**
- tests/test_util/**
- tests/testing_config/**
- tests/util/**
other: &other
- .github/workflows/**
- homeassistant/scripts/**
requirements: &requirements
- .github/workflows/**
- homeassistant/package_constraints.txt
- script/pip_check
- requirements*.txt
- pyproject.toml
any:
- *base_platforms
- *components
- *core
- *other
- *requirements
- *tests
+257 -607
View File
File diff suppressed because it is too large Load Diff
+3 -10
View File
@@ -5,26 +5,19 @@
"postCreateCommand": "script/setup",
"postStartCommand": "script/bootstrap",
"containerEnv": { "DEVCONTAINER": "1" },
"appPort": ["8123:8123"],
"appPort": 8123,
"runArgs": ["-e", "GIT_EDITOR=code --wait"],
"extensions": [
"ms-python.vscode-pylance",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml",
"esbenp.prettier-vscode",
"GitHub.vscode-pull-request-github"
"esbenp.prettier-vscode"
],
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.blackPath": "/usr/local/bin/black",
"python.linting.flake8Path": "/usr/local/bin/flake8",
"python.linting.pycodestylePath": "/usr/local/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/bin/pydocstyle",
"python.linting.mypyPath": "/usr/local/bin/mypy",
"python.linting.pylintPath": "/usr/local/bin/pylint",
"python.linting.enabled": true,
"python.formatting.provider": "black",
"python.testing.pytestArgs": ["--no-cov"],
"editor.formatOnPaste": false,
+1
View File
@@ -9,6 +9,7 @@ docs
.vscode
# Test related files
.tox
tests
# Other virtualization methods
+1 -1
View File
@@ -16,7 +16,7 @@
<!--
Provide details about the versions you are using, which helps us to reproduce
and find the issue quicker. Version information is found in the
Home Assistant frontend: Settings -> About.
Home Assistant frontend: Configuration -> Info.
-->
- Home Assistant Core release with the issue:
+11 -24
View File
@@ -15,7 +15,7 @@ body:
attributes:
label: The problem
description: >-
Describe the issue you are experiencing here, to communicate to the
Describe the issue you are experiencing here to communicate to the
maintainers. Tell us what you were trying to do and what happened.
Provide a clear and concise description of what the problem is.
@@ -28,12 +28,10 @@ body:
validations:
required: true
attributes:
label: What version of Home Assistant Core has the issue?
label: What is version of Home Assistant Core has the issue?
placeholder: core-
description: >
Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).
[![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/)
Can be found in the Configuration panel -> Info.
- type: input
attributes:
label: What was the last working version of Home Assistant Core?
@@ -46,9 +44,7 @@ body:
attributes:
label: What type of installation are you running?
description: >
Can be found in: [Settings -> System-> Repairs -> Three Dots in Upper Right -> System information](https://my.home-assistant.io/redirect/system_health/).
[![Open your Home Assistant instance and show health information about your system.](https://my.home-assistant.io/badges/system_health.svg)](https://my.home-assistant.io/redirect/system_health/)
If you don't know, you can find it in: Configuration panel -> Info.
options:
- Home Assistant OS
- Home Assistant Container
@@ -59,15 +55,15 @@ body:
attributes:
label: Integration causing the issue
description: >
The name of the integration. For example: Automation, Philips Hue
The name of the integration, for example, Automation or Philips Hue.
- type: input
id: integration_link
attributes:
label: Link to integration documentation on our website
placeholder: "https://www.home-assistant.io/integrations/..."
description: |
Providing a link [to the documentation][docs] helps us categorize the
issue, while also providing a useful reference for others.
Providing a link [to the documentation][docs] help us categorizing the
issue, while providing a useful reference at the same time.
[docs]: https://www.home-assistant.io/integrations
@@ -75,23 +71,12 @@ body:
attributes:
value: |
# Details
- type: textarea
attributes:
label: Diagnostics information
placeholder: "drag-and-drop the diagnostics data file here (do not copy-and-paste the content)"
description: >-
Many integrations provide the ability to [download diagnostic data](https://www.home-assistant.io/docs/configuration/troubleshooting/#debug-logs-and-diagnostics).
**It would really help if you could download the diagnostics data for the device you are having issues with,
and <ins>drag-and-drop that file into the textbox below.</ins>**
It generally allows pinpointing defects and thus resolving issues faster.
- type: textarea
attributes:
label: Example YAML snippet
description: |
If applicable, please provide an example piece of YAML that can help reproduce this problem.
This can be from an automation, script, scene or configuration.
If this issue has an example piece of YAML that can help reproducing this problem, please provide.
This can be an piece of YAML from, e.g., an automation, script, scene or configuration.
render: yaml
- type: textarea
attributes:
@@ -103,3 +88,5 @@ body:
label: Additional information
description: >
If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here, by
dragging and dropping files in the field below.
+13 -2
View File
@@ -33,7 +33,6 @@
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!)
- [ ] New feature (which adds functionality to an existing integration)
- [ ] Deprecation (breaking change to happen in the future)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests
@@ -75,6 +74,18 @@ If the code communicates with devices, web services, or third-party tools:
- [ ] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
- [ ] Untested files have been added to `.coveragerc`.
The integration reached or maintains the following [Integration Quality Scale][quality-scale]:
<!--
The Integration Quality Scale scores an integration on the code quality
and user experience. Each level of the quality scale consists of a list
of requirements. We highly recommend getting your integration scored!
-->
- [ ] No score or internal
- [ ] 🥈 Silver
- [ ] 🥇 Gold
- [ ] 🏆 Platinum
<!--
This project is very active and we have a high turnover of pull requests.
@@ -96,7 +107,7 @@ To help with the load of incoming pull requests:
- [ ] I have reviewed two other [open pull requests][prs] in this repository.
[prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone+-status%3Afailure
[prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone
<!--
Thank you for contributing <3
+13
View File
@@ -0,0 +1,13 @@
# Configuration for move-issues - https://github.com/dessant/move-issues
# Delete the command comment. Ignored when the comment also contains other content
deleteCommand: true
# Close the source issue after moving
closeSourceIssue: true
# Lock the source issue after moving
lockSourceIssue: false
# Set custom aliases for targets
# aliases:
# r: repo
# or: owner/repo
+86 -131
View File
@@ -10,12 +10,11 @@ on:
env:
BUILD_TYPE: core
DEFAULT_PYTHON: 3.9
DEFAULT_PYTHON: 3.8
jobs:
init:
name: Initialize build
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
outputs:
architectures: ${{ steps.info.outputs.architectures }}
@@ -24,12 +23,12 @@ jobs:
publish: ${{ steps.version.outputs.publish }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2.3.4
with:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.4.0
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -57,30 +56,29 @@ jobs:
uses: home-assistant/actions/helpers/codenotary@master
with:
source: file://${{ github.workspace }}/OFFICIAL_IMAGE
asset: OFFICIAL_IMAGE-${{ steps.version.outputs.version }}
token: ${{ secrets.CAS_TOKEN }}
user: ${{ secrets.VCN_USER }}
password: ${{ secrets.VCN_PASSWORD }}
organisation: home-assistant.io
build_python:
name: Build PyPi package
needs: init
runs-on: ubuntu-latest
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
if: needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2.3.4
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.4.0
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- 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
pip install twine wheel
python setup.py sdist bdist_wheel
- name: Upload package
shell: bash
@@ -92,7 +90,6 @@ jobs:
build_base:
name: Build ${{ matrix.arch }} base core image
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
strategy:
@@ -100,22 +97,11 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v2
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend
branch: dev
workflow: nightly.yaml
workflow_conclusion: success
name: wheels
uses: actions/checkout@v2.3.4
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v4.4.0
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -123,22 +109,10 @@ jobs:
if: needs.init.outputs.channel == 'dev'
shell: bash
run: |
python3 -m pip install packaging tomli
python3 -m pip install --use-deprecated=legacy-resolver .
version="$(python3 script/version_bump.py nightly)"
if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then
echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}"
frontend_version="${BASH_REMATCH[1]}" yq \
--inplace e -o json \
'.requirements = ["home-assistant-frontend=="+env(frontend_version)]' \
homeassistant/components/frontend/manifest.json
sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \
homeassistant/package_constraints.txt
python -m script.gen_requirements_all
fi
python3 -m pip install packaging
python3 -m pip install .
python3 script/version_bump.py nightly
version="$(python setup.py -V)"
- name: Write meta info file
shell: bash
@@ -146,32 +120,31 @@ jobs:
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.1.0
uses: docker/login-action@v1.10.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2022.11.0
uses: home-assistant/builder@2021.07.0
with:
args: |
$BUILD_ARGS \
--${{ matrix.arch }} \
--target /data \
--with-codenotary "${{ secrets.VCN_USER }}" "${{ secrets.VCN_PASSWORD }}" "${{ secrets.VCN_ORG }}" \
--validate-from "${{ secrets.VCN_ORG }}" \
--generic ${{ needs.init.outputs.version }}
env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
build_machine:
name: Build ${{ matrix.machine }} machine core image
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
runs-on: ubuntu-latest
strategy:
@@ -195,53 +168,40 @@ jobs:
- raspberrypi4
- raspberrypi4-64
- tinker
- yellow
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
- name: Set build additional args
run: |
# Create general tags
if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then
echo "BUILD_ARGS=--additional-tag dev" >> $GITHUB_ENV
elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then
echo "BUILD_ARGS=--additional-tag beta" >> $GITHUB_ENV
else
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
fi
uses: actions/checkout@v2.3.4
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.1.0
uses: docker/login-action@v1.10.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2022.11.0
uses: home-assistant/builder@2021.07.0
with:
args: |
$BUILD_ARGS \
--target /data/machine \
--with-codenotary "${{ secrets.VCN_USER }}" "${{ secrets.VCN_PASSWORD }}" "${{ secrets.VCN_ORG }}" \
--validate-from "${{ secrets.VCN_ORG }}" \
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
publish_ha:
name: Publish version files
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_machine"]
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2.3.4
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
@@ -268,37 +228,28 @@ jobs:
channel: beta
publish_container:
name: Publish meta container for ${{ matrix.registry }}
if: github.repository_owner == 'home-assistant'
name: Publish meta container
needs: ["init", "build_base"]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
registry:
- "ghcr.io/home-assistant"
- "homeassistant"
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2.3.4
- name: Login to DockerHub
if: matrix.registry == 'homeassistant'
uses: docker/login-action@v2.1.0
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
if: matrix.registry == 'ghcr.io/home-assistant'
uses: docker/login-action@v2.1.0
uses: docker/login-action@v1.10.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install CAS tools
uses: home-assistant/actions/helpers/cas@master
- name: Install VCN tools
uses: home-assistant/actions/helpers/vcn@master
- name: Build Meta Image
shell: bash
@@ -306,75 +257,79 @@ jobs:
export DOCKER_CLI_EXPERIMENTAL=enabled
function create_manifest() {
local tag_l=${1}
local tag_r=${2}
local docker_reg=${1}
local tag_l=${2}
local tag_r=${3}
docker manifest create "${{ matrix.registry }}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \
"${{ matrix.registry }}/i386-homeassistant:${tag_r}" \
"${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \
"${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \
"${{ matrix.registry }}/aarch64-homeassistant:${tag_r}"
docker manifest create "${docker_reg}/home-assistant:${tag_l}" \
"${docker_reg}/amd64-homeassistant:${tag_r}" \
"${docker_reg}/i386-homeassistant:${tag_r}" \
"${docker_reg}/armhf-homeassistant:${tag_r}" \
"${docker_reg}/armv7-homeassistant:${tag_r}" \
"${docker_reg}/aarch64-homeassistant:${tag_r}"
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/amd64-homeassistant:${tag_r}" \
docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \
"${docker_reg}/amd64-homeassistant:${tag_r}" \
--os linux --arch amd64
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/i386-homeassistant:${tag_r}" \
docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \
"${docker_reg}/i386-homeassistant:${tag_r}" \
--os linux --arch 386
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/armhf-homeassistant:${tag_r}" \
docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \
"${docker_reg}/armhf-homeassistant:${tag_r}" \
--os linux --arch arm --variant=v6
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/armv7-homeassistant:${tag_r}" \
docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \
"${docker_reg}/armv7-homeassistant:${tag_r}" \
--os linux --arch arm --variant=v7
docker manifest annotate "${{ matrix.registry }}/home-assistant:${tag_l}" \
"${{ matrix.registry }}/aarch64-homeassistant:${tag_r}" \
docker manifest annotate "${docker_reg}/home-assistant:${tag_l}" \
"${docker_reg}/aarch64-homeassistant:${tag_r}" \
--os linux --arch arm64 --variant=v8
docker manifest push --purge "${{ matrix.registry }}/home-assistant:${tag_l}"
docker manifest push --purge "${docker_reg}/home-assistant:${tag_l}"
}
function validate_image() {
local image=${1}
if ! cas authenticate --signerID notary@home-assistant.io "docker://${image}"; then
state="$(vcn authenticate --org home-assistant.io --output json docker://${image} | jq '.verification.status // 2')"
if [[ "${state}" != "0" ]]; then
echo "Invalid signature!"
exit 1
fi
}
docker pull "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
for docker_reg in "homeassistant" "ghcr.io/home-assistant"; do
docker pull "${docker_reg}/amd64-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${docker_reg}/i386-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${docker_reg}/armhf-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${docker_reg}/armv7-homeassistant:${{ needs.init.outputs.version }}"
docker pull "${docker_reg}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${{ matrix.registry }}/amd64-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${{ matrix.registry }}/i386-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${{ matrix.registry }}/armhf-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${{ matrix.registry }}/armv7-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${{ matrix.registry }}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${docker_reg}/amd64-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${docker_reg}/i386-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${docker_reg}/armhf-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${docker_reg}/armv7-homeassistant:${{ needs.init.outputs.version }}"
validate_image "${docker_reg}/aarch64-homeassistant:${{ needs.init.outputs.version }}"
# Create version tag
create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}"
# Create version tag
create_manifest "${docker_reg}" "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}"
# Create general tags
if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then
create_manifest "dev" "${{ needs.init.outputs.version }}"
elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then
create_manifest "beta" "${{ needs.init.outputs.version }}"
create_manifest "rc" "${{ needs.init.outputs.version }}"
else
create_manifest "stable" "${{ needs.init.outputs.version }}"
create_manifest "latest" "${{ needs.init.outputs.version }}"
create_manifest "beta" "${{ needs.init.outputs.version }}"
create_manifest "rc" "${{ needs.init.outputs.version }}"
# Create general tags
if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then
create_manifest "${docker_reg}" "dev" "${{ needs.init.outputs.version }}"
elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then
create_manifest "${docker_reg}" "beta" "${{ needs.init.outputs.version }}"
create_manifest "${docker_reg}" "rc" "${{ needs.init.outputs.version }}"
else
create_manifest "${docker_reg}" "stable" "${{ needs.init.outputs.version }}"
create_manifest "${docker_reg}" "latest" "${{ needs.init.outputs.version }}"
create_manifest "${docker_reg}" "beta" "${{ needs.init.outputs.version }}"
create_manifest "${docker_reg}" "rc" "${{ needs.init.outputs.version }}"
# Create series version tag (e.g. 2021.6)
v="${{ needs.init.outputs.version }}"
create_manifest "${v%.*}" "${{ needs.init.outputs.version }}"
fi
# Create series version tag (e.g. 2021.6)
v="${{ needs.init.outputs.version }}"
create_manifest "${docker_reg}" "${v%.*}" "${{ needs.init.outputs.version }}"
fi
done
+493 -733
View File
File diff suppressed because it is too large Load Diff
+5 -6
View File
@@ -7,15 +7,14 @@ on:
jobs:
lock:
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4.0.0
- uses: dessant/lock-threads@v2.1.2
with:
github-token: ${{ github.token }}
issue-inactive-days: "30"
exclude-issue-created-before: "2020-10-01T00:00:00Z"
issue-lock-inactive-days: "30"
issue-exclude-created-before: "2020-10-01T00:00:00Z"
issue-lock-reason: ""
pr-inactive-days: "1"
exclude-pr-created-before: "2020-11-01T00:00:00Z"
pr-lock-inactive-days: "1"
pr-exclude-created-before: "2020-11-01T00:00:00Z"
pr-lock-reason: ""
@@ -1,18 +0,0 @@
{
"problemMatcher": [
{
"owner": "python",
"pattern": [
{
"regexp": "^=+ slowest durations =+$"
},
{
"regexp": "^((.*s)\\s(call|setup|teardown)\\s+(.*)::(.*))$",
"message": 1,
"file": 2,
"loop": true
}
]
}
]
}
+3 -4
View File
@@ -8,7 +8,6 @@ on:
jobs:
stale:
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
steps:
# The 90 day stale policy
@@ -17,7 +16,7 @@ jobs:
# - No PRs marked as no-stale
# - No issues marked as no-stale or help-wanted
- name: 90 days stale issues & PRs policy
uses: actions/stale@v7.0.0
uses: actions/stale@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90
@@ -54,7 +53,7 @@ jobs:
# - No PRs marked as no-stale or new-integrations
# - No issues (-1)
- name: 30 days stale PRs policy
uses: actions/stale@v7.0.0
uses: actions/stale@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30
@@ -79,7 +78,7 @@ jobs:
# - No Issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: Needs more information stale issues policy
uses: actions/stale@v7.0.0
uses: actions/stale@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "needs-more-information"
+6 -7
View File
@@ -12,19 +12,18 @@ on:
- "**strings.json"
env:
DEFAULT_PYTHON: 3.9
DEFAULT_PYTHON: 3.8
jobs:
upload:
name: Upload
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.4.0
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -36,14 +35,14 @@ jobs:
download:
name: Download
needs: upload
if: github.repository_owner == 'home-assistant' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.4.0
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
+44 -53
View File
@@ -16,13 +16,12 @@ on:
jobs:
init:
name: Initialize wheels builder
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
outputs:
architectures: ${{ steps.info.outputs.architectures }}
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2.3.4
- name: Get information
id: info
@@ -43,88 +42,81 @@ jobs:
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
# GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs
# execinfo-dev when building wheels. The setuptools build setup does not have an option for
# adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0)
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo"
# Fix out of memory issues with rust
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
# OpenCV headless installation
echo "CI_BUILD=1"
echo "ENABLE_HEADLESS=1"
) > .env_file
- name: Upload env_file
uses: actions/upload-artifact@v3.1.1
uses: actions/upload-artifact@v2.2.4
with:
name: env_file
path: ./.env_file
- name: Upload requirements_diff
uses: actions/upload-artifact@v3.1.1
uses: actions/upload-artifact@v2.2.4
with:
name: requirements_diff
path: ./requirements_diff.txt
core:
name: Build musllinux wheels with musllinux_1_2 / cp310 at ${{ matrix.arch }} for core
if: github.repository_owner == 'home-assistant'
name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for core
needs: init
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2.3.4
- name: Download env_file
uses: actions/download-artifact@v3
uses: actions/download-artifact@v2
with:
name: env_file
- name: Download requirements_diff
uses: actions/download-artifact@v3
uses: actions/download-artifact@v2
with:
name: requirements_diff
- name: Build wheels
uses: home-assistant/wheels@2022.10.1
uses: home-assistant/wheels@2021.07.0
with:
abi: cp310
tag: musllinux_1_2
tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }}
wheels-host: wheels.hass.io
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
env-file: true
apk: "libffi-dev;openssl-dev;yaml-dev"
apk: "build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev"
pip: "Cython;numpy"
skip-binary: aiohttp
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements-diff: 'requirements_diff.txt'
requirements: "requirements.txt"
integrations:
name: Build musllinux wheels with musllinux_1_2 / cp310 at ${{ matrix.arch }} for integrations
if: github.repository_owner == 'home-assistant'
name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for integrations
needs: init
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v2.3.4
- name: Download env_file
uses: actions/download-artifact@v3
uses: actions/download-artifact@v2
with:
name: env_file
- name: Download requirements_diff
uses: actions/download-artifact@v3
uses: actions/download-artifact@v2
with:
name: requirements_diff
@@ -133,43 +125,42 @@ jobs:
requirement_files="requirements_all.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
sed -i "s|# bluepy|bluepy|g" ${requirement_file}
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
sed -i "s|# RPi.GPIO|RPi.GPIO|g" ${requirement_file}
sed -i "s|# raspihats|raspihats|g" ${requirement_file}
sed -i "s|# rpi-rf|rpi-rf|g" ${requirement_file}
sed -i "s|# blinkt|blinkt|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# smbus-cffi|smbus-cffi|g" ${requirement_file}
sed -i "s|# i2csense|i2csense|g" ${requirement_file}
sed -i "s|# python-eq3bt|python-eq3bt|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
sed -i "s|# decora|decora|g" ${requirement_file}
sed -i "s|# avion|avion|g" ${requirement_file}
sed -i "s|# PySwitchbot|PySwitchbot|g" ${requirement_file}
sed -i "s|# pySwitchmate|pySwitchmate|g" ${requirement_file}
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
sed -i "s|# bme680|bme680|g" ${requirement_file}
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
done
- name: Adjust build env
run: |
if [ "${{ matrix.arch }}" = "i386" ]; then
echo "NPY_DISABLE_SVML=1" >> .env_file
fi
(
# cmake > 3.22.2 have issue on arm
# Tested until 3.22.5
echo "cmake==3.22.2"
) >> homeassistant/package_constraints.txt
# Do not pin numpy in wheels building
sed -i "/numpy/d" homeassistant/package_constraints.txt
- name: Build wheels
uses: home-assistant/wheels@2022.10.1
uses: home-assistant/wheels@2021.07.0
with:
abi: cp310
tag: musllinux_1_2
tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }}
wheels-host: wheels.hass.io
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
env-file: true
apk: "libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev"
skip-binary: aiohttp;grpcio
legacy: true
apk: "build-base;cmake;git;linux-headers;libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev"
pip: "Cython;numpy;scikit-build"
skip-binary: aiohttp
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements-diff: 'requirements_diff.txt'
requirements: "requirements_all.txt"
+2 -1
View File
@@ -1,4 +1,4 @@
/config
config/*
config2/*
tests/testing_config/deps
@@ -58,6 +58,7 @@ pip-log.txt
# Unit test / coverage reports
.coverage
.tox
coverage.xml
nosetests.xml
htmlcov/
+6
View File
@@ -0,0 +1,6 @@
# Patterns matched in this file will be ignored by supported search utilities
# Ignore generated html and javascript files
/homeassistant/components/frontend/www_static/*.html
/homeassistant/components/frontend/www_static/*.js
/homeassistant/components/frontend/www_static/panels/*.html
+25 -51
View File
@@ -1,48 +1,42 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v2.23.3
hooks:
- id: pyupgrade
args: [--py39-plus]
- repo: https://github.com/PyCQA/autoflake
rev: v2.0.0
hooks:
- id: autoflake
args:
- --in-place
- --remove-all-unused-imports
args: [--py38-plus]
- repo: https://github.com/psf/black
rev: 22.12.0
rev: 21.7b0
hooks:
- id: black
args:
- --safe
- --quiet
files: ^((homeassistant|pylint|script|tests)/.+)?[^/]+\.py$
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
rev: v2.0.0
hooks:
- id: codespell
args:
- --ignore-words-list=additionals,alle,alot,ba,bre,bund,datas,dof,dur,ether,farenheit,falsy,fo,haa,hass,hist,iam,iff,iif,incomfort,ines,ist,lightsensor,mut,nam,nd,pres,pullrequests,referer,resset,rime,ser,serie,sur,te,technik,ue,uint,unsecure,visability,wan,wanna,withing,zar
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
exclude: ^tests/fixtures/|homeassistant/generated/
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
exclude: ^tests/fixtures/
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies:
- pycodestyle==2.10.0
- pyflakes==3.0.1
- pycodestyle==2.7.0
- pyflakes==2.3.1
- flake8-docstrings==1.6.0
- pydocstyle==6.1.1
- flake8-comprehensions==3.10.1
- flake8-noqa==1.3.0
- mccabe==0.7.0
- pydocstyle==6.0.0
- flake8-comprehensions==3.5.0
- flake8-noqa==1.1.0
- mccabe==0.6.1
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.7.4
rev: 1.7.0
hooks:
- id: bandit
args:
@@ -51,7 +45,7 @@ repos:
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.11.4
rev: 5.9.3
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -67,24 +61,24 @@ repos:
- --branch=master
- --branch=rc
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.28.0
rev: v1.26.1
hooks:
- id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
rev: v2.2.1
hooks:
- id: prettier
stages: [manual]
- repo: https://github.com/cdce8p/python-typing-update
rev: v0.5.0
rev: v0.3.5
hooks:
# Run `python-typing-update` hook manually from time to time
# to update python typing syntax.
# Will require manual work, before submitting changes!
# pre-commit run --hook-stage manual python-typing-update --all-files
- id: python-typing-update
stages: [manual]
args:
- --py39-plus
- --py38-plus
- --force
- --keep-updates
files: ^(homeassistant|tests|script)/.+\.py$
@@ -100,12 +94,6 @@ repos:
language: script
types: [python]
require_serial: true
files: ^(homeassistant|pylint)/.+\.py$
- id: pylint
name: pylint
entry: script/run-in-env.sh pylint -j 0 --ignore-missing-annotations=y
language: script
types: [python]
files: ^homeassistant/.+\.py$
- id: gen_requirements_all
name: gen_requirements_all
@@ -113,25 +101,11 @@ repos:
pass_filenames: false
language: script
types: [text]
files: ^(homeassistant/.+/manifest\.json|homeassistant/brands/.+\.json|pyproject\.toml|\.pre-commit-config\.yaml|script/gen_requirements_all\.py)$
files: ^(homeassistant/.+/manifest\.json|\.pre-commit-config\.yaml)$
- id: hassfest
name: hassfest
entry: script/run-in-env.sh python3 -m script.hassfest
pass_filenames: false
language: script
types: [text]
files: ^(homeassistant/.+/(manifest|strings)\.json|homeassistant/brands/.*\.json|\.coveragerc|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py)$
- id: hassfest-metadata
name: hassfest-metadata
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata
pass_filenames: false
language: script
types: [text]
files: ^(script/hassfest/metadata\.py|homeassistant/const\.py$|pyproject\.toml)$
- id: hassfest-mypy-config
name: hassfest-mypy-config
entry: script/run-in-env.sh python3 -m script.hassfest -p mypy_config
pass_filenames: false
language: script
types: [text]
files: ^(script/hassfest/mypy_config\.py|\.strict-typing|mypy\.ini)$
files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc|homeassistant/.+/services\.yaml)$
+1 -2
View File
@@ -1,6 +1,5 @@
*.md
.strict-typing
azure-*.yml
docs/source/_templates/*
homeassistant/components/*/translations/*.json
homeassistant/generated/*
tests/fixtures/*
+5 -9
View File
@@ -1,14 +1,10 @@
# .readthedocs.yml
version: 2
build:
os: ubuntu-20.04
tools:
python: "3.9"
image: latest
python:
install:
- method: setuptools
path: .
- requirements: requirements_docs.txt
version: 3.8
setup_py_install: true
requirements_file: requirements_docs.txt
+10 -218
View File
@@ -2,323 +2,115 @@
# If component is fully covered with type annotations, please add it here
# to enable strict mypy checks.
# Strict typing is enabled by default for core files.
# Add it here to add 'disallow_any_generics'.
# --- Only for core file! ---
homeassistant.auth.auth_store
homeassistant.auth.providers.*
homeassistant.core
homeassistant.exceptions
homeassistant.helpers.area_registry
homeassistant.helpers.condition
homeassistant.helpers.debounce
homeassistant.helpers.deprecation
homeassistant.helpers.device_registry
homeassistant.helpers.discovery
homeassistant.helpers.dispatcher
homeassistant.helpers.entity
homeassistant.helpers.entity_platform
homeassistant.helpers.entity_values
homeassistant.helpers.event
homeassistant.helpers.reload
homeassistant.helpers.script_variables
homeassistant.helpers.singleton
homeassistant.helpers.sun
homeassistant.helpers.translation
homeassistant.loader
homeassistant.requirements
homeassistant.runner
homeassistant.setup
homeassistant.util.async_
homeassistant.util.color
homeassistant.util.decorator
homeassistant.util.location
homeassistant.util.logging
homeassistant.util.process
homeassistant.util.unit_system
# --- Add components below this line ---
homeassistant.components
homeassistant.components.abode.*
homeassistant.components.accuweather.*
homeassistant.components.acer_projector.*
homeassistant.components.accuweather.*
homeassistant.components.actiontec.*
homeassistant.components.adguard.*
homeassistant.components.aftership.*
homeassistant.components.air_quality.*
homeassistant.components.airly.*
homeassistant.components.airvisual.*
homeassistant.components.airzone.*
homeassistant.components.aladdin_connect.*
homeassistant.components.alarm_control_panel.*
homeassistant.components.alert.*
homeassistant.components.amazon_polly.*
homeassistant.components.ambee.*
homeassistant.components.ambient_station.*
homeassistant.components.amcrest.*
homeassistant.components.ampio.*
homeassistant.components.analytics.*
homeassistant.components.anthemav.*
homeassistant.components.aqualogic.*
homeassistant.components.aseko_pool_live.*
homeassistant.components.asuswrt.*
homeassistant.components.auth.*
homeassistant.components.automation.*
homeassistant.components.awair.*
homeassistant.components.backup.*
homeassistant.components.baf.*
homeassistant.components.bayesian.*
homeassistant.components.binary_sensor.*
homeassistant.components.blockchain.*
homeassistant.components.bluetooth.*
homeassistant.components.bluetooth_tracker.*
homeassistant.components.bmw_connected_drive.*
homeassistant.components.bond.*
homeassistant.components.braviatv.*
homeassistant.components.brother.*
homeassistant.components.browser.*
homeassistant.components.button.*
homeassistant.components.calendar.*
homeassistant.components.camera.*
homeassistant.components.canary.*
homeassistant.components.clickatell.*
homeassistant.components.clicksend.*
homeassistant.components.cover.*
homeassistant.components.cpuspeed.*
homeassistant.components.crownstone.*
homeassistant.components.deconz.*
homeassistant.components.demo.*
homeassistant.components.derivative.*
homeassistant.components.device_automation.*
homeassistant.components.device_tracker.*
homeassistant.components.devolo_home_control.*
homeassistant.components.devolo_home_network.*
homeassistant.components.dhcp.*
homeassistant.components.diagnostics.*
homeassistant.components.dlna_dmr.*
homeassistant.components.dnsip.*
homeassistant.components.dsmr.*
homeassistant.components.dunehd.*
homeassistant.components.efergy.*
homeassistant.components.elgato.*
homeassistant.components.elkm1.*
homeassistant.components.emulated_hue.*
homeassistant.components.energy.*
homeassistant.components.esphome.*
homeassistant.components.evil_genius_labs.*
homeassistant.components.fan.*
homeassistant.components.energy.*
homeassistant.components.fastdotcom.*
homeassistant.components.feedreader.*
homeassistant.components.file_upload.*
homeassistant.components.filesize.*
homeassistant.components.fitbit.*
homeassistant.components.flux_led.*
homeassistant.components.flunearyou.*
homeassistant.components.forecast_solar.*
homeassistant.components.fritz.*
homeassistant.components.fritzbox.*
homeassistant.components.fritzbox_callmonitor.*
homeassistant.components.fronius.*
homeassistant.components.frontend.*
homeassistant.components.fully_kiosk.*
homeassistant.components.fritz.*
homeassistant.components.geo_location.*
homeassistant.components.geocaching.*
homeassistant.components.gios.*
homeassistant.components.goalzero.*
homeassistant.components.google.*
homeassistant.components.google_sheets.*
homeassistant.components.greeneye_monitor.*
homeassistant.components.group.*
homeassistant.components.guardian.*
homeassistant.components.hardkernel.*
homeassistant.components.hardware.*
homeassistant.components.here_travel_time.*
homeassistant.components.history.*
homeassistant.components.homeassistant.triggers.event
homeassistant.components.homeassistant_alerts.*
homeassistant.components.homeassistant_hardware.*
homeassistant.components.homeassistant_sky_connect.*
homeassistant.components.homeassistant_yellow.*
homeassistant.components.homekit
homeassistant.components.homekit.accessories
homeassistant.components.homekit.aidmanager
homeassistant.components.homekit.config_flow
homeassistant.components.homekit.diagnostics
homeassistant.components.homekit.logbook
homeassistant.components.homekit.type_locks
homeassistant.components.homekit.type_triggers
homeassistant.components.homekit.util
homeassistant.components.homekit_controller
homeassistant.components.homekit_controller.alarm_control_panel
homeassistant.components.homekit_controller.button
homeassistant.components.homekit_controller.config_flow
homeassistant.components.homekit_controller.const
homeassistant.components.homekit_controller.lock
homeassistant.components.homekit_controller.select
homeassistant.components.homekit_controller.storage
homeassistant.components.homekit_controller.utils
homeassistant.components.homewizard.*
homeassistant.components.http.*
homeassistant.components.huawei_lte.*
homeassistant.components.hyperion.*
homeassistant.components.ibeacon.*
homeassistant.components.image_processing.*
homeassistant.components.image_upload.*
homeassistant.components.input_button.*
homeassistant.components.input_select.*
homeassistant.components.integration.*
homeassistant.components.iqvia.*
homeassistant.components.isy994.*
homeassistant.components.jellyfin.*
homeassistant.components.jewish_calendar.*
homeassistant.components.kaleidescape.*
homeassistant.components.knx.*
homeassistant.components.kraken.*
homeassistant.components.lacrosse_view.*
homeassistant.components.lametric.*
homeassistant.components.laundrify.*
homeassistant.components.lcn.*
homeassistant.components.lidarr.*
homeassistant.components.lifx.*
homeassistant.components.light.*
homeassistant.components.litterrobot.*
homeassistant.components.local_ip.*
homeassistant.components.lock.*
homeassistant.components.logbook.*
homeassistant.components.logger.*
homeassistant.components.lookin.*
homeassistant.components.luftdaten.*
homeassistant.components.mailbox.*
homeassistant.components.mastodon.*
homeassistant.components.matter.*
homeassistant.components.media_player.*
homeassistant.components.media_source.*
homeassistant.components.metoffice.*
homeassistant.components.mikrotik.*
homeassistant.components.min_max.*
homeassistant.components.mjpeg.*
homeassistant.components.modbus.*
homeassistant.components.modem_callerid.*
homeassistant.components.moon.*
homeassistant.components.mqtt.*
homeassistant.components.mysensors.*
homeassistant.components.nam.*
homeassistant.components.nanoleaf.*
homeassistant.components.neato.*
homeassistant.components.nest.*
homeassistant.components.netatmo.*
homeassistant.components.network.*
homeassistant.components.nextdns.*
homeassistant.components.nfandroidtv.*
homeassistant.components.nissan_leaf.*
homeassistant.components.no_ip.*
homeassistant.components.notify.*
homeassistant.components.notion.*
homeassistant.components.number.*
homeassistant.components.nut.*
homeassistant.components.oncue.*
homeassistant.components.onewire.*
homeassistant.components.open_meteo.*
homeassistant.components.openexchangerates.*
homeassistant.components.openuv.*
homeassistant.components.overkiz.*
homeassistant.components.peco.*
homeassistant.components.persistent_notification.*
homeassistant.components.pi_hole.*
homeassistant.components.powerwall.*
homeassistant.components.proximity.*
homeassistant.components.prusalink.*
homeassistant.components.pure_energie.*
homeassistant.components.purpleair.*
homeassistant.components.pvoutput.*
homeassistant.components.qnap_qsw.*
homeassistant.components.radarr.*
homeassistant.components.rainmachine.*
homeassistant.components.raspberry_pi.*
homeassistant.components.rdw.*
homeassistant.components.recollect_waste.*
homeassistant.components.recorder.*
homeassistant.components.recorder.purge
homeassistant.components.recorder.repack
homeassistant.components.recorder.statistics
homeassistant.components.remote.*
homeassistant.components.renault.*
homeassistant.components.repairs.*
homeassistant.components.rfxtrx.*
homeassistant.components.rhasspy.*
homeassistant.components.ridwell.*
homeassistant.components.rituals_perfume_genie.*
homeassistant.components.roku.*
homeassistant.components.rpi_power.*
homeassistant.components.rtsp_to_webrtc.*
homeassistant.components.ruuvitag_ble.*
homeassistant.components.samsungtv.*
homeassistant.components.scene.*
homeassistant.components.schedule.*
homeassistant.components.select.*
homeassistant.components.senseme.*
homeassistant.components.sensibo.*
homeassistant.components.sensirion_ble.*
homeassistant.components.sensor.*
homeassistant.components.senz.*
homeassistant.components.shelly.*
homeassistant.components.simplepush.*
homeassistant.components.simplisafe.*
homeassistant.components.skybell.*
homeassistant.components.slack.*
homeassistant.components.sleepiq.*
homeassistant.components.smhi.*
homeassistant.components.snooz.*
homeassistant.components.sonarr.*
homeassistant.components.speedtestdotnet.*
homeassistant.components.sonos.media_player
homeassistant.components.ssdp.*
homeassistant.components.statistics.*
homeassistant.components.steamist.*
homeassistant.components.stookalert.*
homeassistant.components.stream.*
homeassistant.components.sun.*
homeassistant.components.surepetcare.*
homeassistant.components.switch.*
homeassistant.components.switchbee.*
homeassistant.components.switcher_kis.*
homeassistant.components.synology_dsm.*
homeassistant.components.systemmonitor.*
homeassistant.components.tag.*
homeassistant.components.tailscale.*
homeassistant.components.tautulli.*
homeassistant.components.tcp.*
homeassistant.components.tibber.*
homeassistant.components.tile.*
homeassistant.components.tilt_ble.*
homeassistant.components.tolo.*
homeassistant.components.tplink.*
homeassistant.components.tractive.*
homeassistant.components.tradfri.*
homeassistant.components.trafikverket_ferry.*
homeassistant.components.trafikverket_train.*
homeassistant.components.trafikverket_weatherstation.*
homeassistant.components.tts.*
homeassistant.components.twentemilieu.*
homeassistant.components.unifi.update
homeassistant.components.unifiprotect.*
homeassistant.components.upcloud.*
homeassistant.components.update.*
homeassistant.components.uptime.*
homeassistant.components.uptimerobot.*
homeassistant.components.usb.*
homeassistant.components.vacuum.*
homeassistant.components.vallox.*
homeassistant.components.velbus.*
homeassistant.components.vlc_telnet.*
homeassistant.components.wake_on_lan.*
homeassistant.components.wallbox.*
homeassistant.components.water_heater.*
homeassistant.components.watttime.*
homeassistant.components.weather.*
homeassistant.components.webostv.*
homeassistant.components.websocket_api.*
homeassistant.components.wemo.*
homeassistant.components.whois.*
homeassistant.components.wiz.*
homeassistant.components.wled.*
homeassistant.components.worldclock.*
homeassistant.components.yale_smart_alarm.*
homeassistant.components.zeroconf.*
homeassistant.components.zodiac.*
homeassistant.components.zeroconf.*
homeassistant.components.zone.*
homeassistant.components.zwave_js.*
-8
View File
@@ -12,14 +12,6 @@
"justMyCode": false,
"args": ["--debug", "-c", "config"]
},
{
"name": "Home Assistant (skip pip)",
"type": "python",
"request": "launch",
"module": "homeassistant",
"justMyCode": false,
"args": ["--debug", "-c", "config", "--skip-pip"]
},
{
// Debug by attaching to local Home Asistant server using Remote Python Debugger.
// See https://www.home-assistant.io/integrations/debugpy/
+9 -20
View File
@@ -16,7 +16,9 @@
"label": "Pytest",
"type": "shell",
"command": "pytest --timeout=10 tests",
"dependsOn": ["Install all Test Requirements"],
"dependsOn": [
"Install all Test Requirements"
],
"group": {
"kind": "test",
"isDefault": true
@@ -45,7 +47,9 @@
"label": "Pylint",
"type": "shell",
"command": "pylint homeassistant",
"dependsOn": ["Install all Requirements"],
"dependsOn": [
"Install all Requirements"
],
"group": {
"kind": "test",
"isDefault": true
@@ -60,7 +64,7 @@
"label": "Code Coverage",
"detail": "Generate code coverage report for a given integration.",
"type": "shell",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0 --numprocesses=auto",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing",
"group": {
"kind": "test",
"isDefault": true
@@ -88,7 +92,7 @@
{
"label": "Install all Requirements",
"type": "shell",
"command": "pip3 install --use-deprecated=legacy-resolver -r requirements_all.txt",
"command": "pip3 install -r requirements_all.txt",
"group": {
"kind": "build",
"isDefault": true
@@ -102,22 +106,7 @@
{
"label": "Install all Test Requirements",
"type": "shell",
"command": "pip3 install --use-deprecated=legacy-resolver -r requirements_test_all.txt",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Compile translations",
"detail": "In order to test changes to translation files, the translation strings must be compiled into Home Assistant's translation directories.",
"type": "shell",
"command": "python3 -m script.translations develop --integration ${input:integrationName}",
"command": "pip3 install -r requirements_test_all.txt",
"group": {
"kind": "build",
"isDefault": true
+596 -1360
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -123,7 +123,7 @@ enforcement ladder][mozilla].
## Adoption
This Code of Conduct was first adopted on January 21st, 2017, and announced in
This Code of Conduct was first adopted January 21st, 2017 and announced in
[this][coc-blog] blog post and has been updated on May 25th, 2020 to version
2.0 of the [Contributor Covenant][homepage] as announced in [this][coc2-blog]
blog post.
+21 -41
View File
@@ -7,49 +7,29 @@ ENV \
WORKDIR /usr/src
## Setup Home Assistant Core dependencies
COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
RUN \
pip3 install \
--no-cache-dir \
--no-index \
--only-binary=:all: \
--find-links "${WHEELS_LINKS}" \
--use-deprecated=legacy-resolver \
-r homeassistant/requirements.txt
COPY requirements_all.txt home_assistant_frontend-* homeassistant/
RUN \
if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \
pip3 install \
--no-cache-dir \
--no-index \
homeassistant/home_assistant_frontend-*.whl; \
fi \
&& \
LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" \
MALLOC_CONF="background_thread:true,metadata_thp:auto,dirty_decay_ms:20000,muzzy_decay_ms:20000" \
pip3 install \
--no-cache-dir \
--no-index \
--only-binary=:all: \
--find-links "${WHEELS_LINKS}" \
--use-deprecated=legacy-resolver \
-r homeassistant/requirements_all.txt
## Setup Home Assistant Core
## Setup Home Assistant
COPY . homeassistant/
RUN \
pip3 install \
--no-cache-dir \
--no-index \
--only-binary=:all: \
--find-links "${WHEELS_LINKS}" \
--use-deprecated=legacy-resolver \
-e ./homeassistant \
&& python3 -m compileall \
homeassistant/homeassistant
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements_all.txt \
&& pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-e ./homeassistant \
&& python3 -m compileall homeassistant/homeassistant
# Fix Bug with Alpine 3.14 and sqlite 3.35
# https://gitlab.alpinelinux.org/alpine/aports/-/issues/12524
ARG BUILD_ARCH
RUN \
if [ "${BUILD_ARCH}" = "amd64" ]; then \
export APK_ARCH=x86_64; \
elif [ "${BUILD_ARCH}" = "i386" ]; then \
export APK_ARCH=x86; \
else \
export APK_ARCH=${BUILD_ARCH}; \
fi \
&& curl -O http://dl-cdn.alpinelinux.org/alpine/v3.13/main/${APK_ARCH}/sqlite-libs-3.34.1-r0.apk \
&& apk add --no-cache sqlite-libs-3.34.1-r0.apk \
&& rm -f sqlite-libs-3.34.1-r0.apk
# Home Assistant S6-Overlay
COPY rootfs /
+4 -18
View File
@@ -2,15 +2,6 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Uninstall pre-installed formatting and linting tools
# They would conflict with our pinned versions
RUN pipx uninstall black
RUN pipx uninstall flake8
RUN pipx uninstall pydocstyle
RUN pipx uninstall pycodestyle
RUN pipx uninstall mypy
RUN pipx uninstall pylint
RUN \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& apt-get update \
@@ -26,11 +17,7 @@ RUN \
libswresample-dev \
libavfilter-dev \
libpcap-dev \
libturbojpeg0 \
libyaml-dev \
libxml2 \
git \
cmake \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
@@ -43,12 +30,11 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
WORKDIR /workspaces
# Install Python dependencies from requirements
COPY requirements.txt ./
COPY requirements.txt requirements_test.txt requirements_test_pre_commit.txt ./
COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt
RUN pip3 install -r requirements.txt --use-deprecated=legacy-resolver
COPY requirements_test.txt requirements_test_pre_commit.txt ./
RUN pip3 install -r requirements_test.txt --use-deprecated=legacy-resolver
RUN rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/
RUN pip3 install -r requirements.txt \
&& pip3 install -r requirements_test.txt \
&& rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/
# Set the default shell to bash instead of sh
ENV SHELL /bin/bash
+1
View File
@@ -1,3 +1,4 @@
include README.rst
include LICENSE.md
graft homeassistant
recursive-exclude * *.py[co]
+3 -3
View File
@@ -12,7 +12,7 @@ demo <https://home-assistant.io/demo/>`__, `installation instructions <https://h
Featured integrations
---------------------
|screenshot-integrations|
|screenshot-components|
The system is built using a modular approach so support for other devices or actions can be implemented easily. See also the `section on architecture <https://developers.home-assistant.io/docs/architecture_index/>`__ and the `section on creating your own
components <https://developers.home-assistant.io/docs/creating_component_index/>`__.
@@ -22,7 +22,7 @@ of a component, check the `Home Assistant help section <https://home-assistant.i
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
:target: https://discord.gg/c5DvZ4e
.. |screenshot-states| image:: https://raw.githubusercontent.com/home-assistant/core/master/docs/screenshots.png
.. |screenshot-states| image:: https://raw.github.com/home-assistant/home-assistant/master/docs/screenshots.png
:target: https://home-assistant.io/demo/
.. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/docs/screenshot-integrations.png
.. |screenshot-components| image:: https://raw.github.com/home-assistant/home-assistant/dev/docs/screenshot-components.png
:target: https://home-assistant.io/integrations/
+22
View File
@@ -0,0 +1,22 @@
{
"image": "homeassistant/{arch}-homeassistant",
"shadow_repository": "ghcr.io/home-assistant",
"build_from": {
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.08.0",
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.08.0",
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.08.0",
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.08.0",
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.08.0"
},
"labels": {
"io.hass.type": "core",
"org.opencontainers.image.title": "Home Assistant",
"org.opencontainers.image.description": "Open-source home automation platform running on Python 3",
"org.opencontainers.image.source": "https://github.com/home-assistant/core",
"org.opencontainers.image.authors": "The Home Assistant Authors",
"org.opencontainers.image.url": "https://www.home-assistant.io/",
"org.opencontainers.image.documentation": "https://www.home-assistant.io/docs/",
"org.opencontainers.image.licenses": "Apache License 2.0"
},
"version_tag": true
}
-20
View File
@@ -1,20 +0,0 @@
image: homeassistant/{arch}-homeassistant
shadow_repository: ghcr.io/home-assistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.11.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.11.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.11.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.11.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.11.0
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io
labels:
io.hass.type: core
org.opencontainers.image.title: Home Assistant
org.opencontainers.image.description: Open-source home automation platform running on Python 3
org.opencontainers.image.source: https://github.com/home-assistant/core
org.opencontainers.image.authors: The Home Assistant Authors
org.opencontainers.image.url: https://www.home-assistant.io/
org.opencontainers.image.documentation: https://www.home-assistant.io/docs/
org.opencontainers.image.licenses: Apache License 2.0
-24
View File
@@ -6,28 +6,4 @@ coverage:
default:
target: 90
threshold: 0.09
config-flows:
target: auto
threshold: 1
paths:
- homeassistant/components/*/config_flow.py
patch:
default:
target: auto
config-flows:
target: 100
threshold: 0
paths:
- homeassistant/components/*/config_flow.py
comment: false
# To make partial tests possible,
# we need to carry forward.
flag_management:
default_rules:
carryforward: false
individual_flags:
- name: full-suite
paths:
- ".*"
carryforward: true
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

+158 -50
View File
@@ -2,21 +2,13 @@
from __future__ import annotations
import argparse
import faulthandler
import os
import platform
import subprocess
import sys
import threading
from .const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
FAULT_LOG_FILENAME = "home-assistant.log.fault"
def validate_os() -> None:
"""Validate that Home Assistant is running in a supported operating system."""
if not sys.platform.startswith(("darwin", "linux")):
print("Home Assistant only supports Linux, OSX and Windows using WSL")
sys.exit(1)
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
def validate_python() -> None:
@@ -32,7 +24,7 @@ def validate_python() -> None:
def ensure_config_path(config_dir: str) -> None:
"""Validate the configuration directory."""
# pylint: disable=import-outside-toplevel
from . import config as config_util
import homeassistant.config as config_util
lib_dir = os.path.join(config_dir, "deps")
@@ -66,11 +58,10 @@ def ensure_config_path(config_dir: str) -> None:
def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
# pylint: disable=import-outside-toplevel
from . import config as config_util
import homeassistant.config as config_util
parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.",
epilog=f"If restart is requested, exits with code {RESTART_EXIT_CODE}",
description="Home Assistant: Observe, Control, Automate."
)
parser.add_argument("--version", action="version", version=__version__)
parser.add_argument(
@@ -89,24 +80,20 @@ def get_arguments() -> argparse.Namespace:
parser.add_argument(
"--open-ui", action="store_true", help="Open the webinterface in a browser"
)
skip_pip_group = parser.add_mutually_exclusive_group()
skip_pip_group.add_argument(
parser.add_argument(
"--skip-pip",
action="store_true",
help="Skips pip install of required packages on startup",
)
skip_pip_group.add_argument(
"--skip-pip-packages",
metavar="package_names",
type=lambda arg: arg.split(","),
default=[],
help="Skip pip install of specific packages on startup",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="Enable verbose logging to file."
)
parser.add_argument(
"--pid-file",
metavar="path_to_pid_file",
default=None,
help="Path to PID file useful for running as daemon",
)
parser.add_argument(
"--log-rotate-days",
type=int,
@@ -123,31 +110,123 @@ def get_arguments() -> argparse.Namespace:
"--log-no-color", action="store_true", help="Disable color logs"
)
parser.add_argument(
"--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts"
"--runner",
action="store_true",
help=f"On restart exit with code {RESTART_EXIT_CODE}",
)
parser.add_argument(
"--ignore-os-check",
action="store_true",
help="Skips validation of operating system",
"--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts"
)
if os.name == "posix":
parser.add_argument(
"--daemon", action="store_true", help="Run Home Assistant as daemon"
)
arguments = parser.parse_args()
if os.name != "posix" or arguments.debug or arguments.runner:
setattr(arguments, "daemon", False)
return arguments
def daemonize() -> None:
"""Move current process to daemon process."""
# Create first fork
pid = os.fork()
if pid > 0:
sys.exit(0)
# Decouple fork
os.setsid()
# Create second fork
pid = os.fork()
if pid > 0:
sys.exit(0)
# redirect standard file descriptors to devnull
# pylint: disable=consider-using-with
infd = open(os.devnull, encoding="utf8")
outfd = open(os.devnull, "a+", encoding="utf8")
sys.stdout.flush()
sys.stderr.flush()
os.dup2(infd.fileno(), sys.stdin.fileno())
os.dup2(outfd.fileno(), sys.stdout.fileno())
os.dup2(outfd.fileno(), sys.stderr.fileno())
def check_pid(pid_file: str) -> None:
"""Check that Home Assistant is not already running."""
# Check pid file
try:
with open(pid_file, encoding="utf8") as file:
pid = int(file.readline())
except OSError:
# PID File does not exist
return
# If we just restarted, we just found our own pidfile.
if pid == os.getpid():
return
try:
os.kill(pid, 0)
except OSError:
# PID does not exist
return
print("Fatal Error: Home Assistant is already running.")
sys.exit(1)
def write_pid(pid_file: str) -> None:
"""Create a PID File."""
pid = os.getpid()
try:
with open(pid_file, "w", encoding="utf8") as file:
file.write(str(pid))
except OSError:
print(f"Fatal Error: Unable to write pid file {pid_file}")
sys.exit(1)
def closefds_osx(min_fd: int, max_fd: int) -> None:
"""Make sure file descriptors get closed when we restart.
We cannot call close on guarded fds, and we cannot easily test which fds
are guarded. But we can set the close-on-exec flag on everything we want to
get rid of.
"""
# pylint: disable=import-outside-toplevel
from fcntl import F_GETFD, F_SETFD, FD_CLOEXEC, fcntl
for _fd in range(min_fd, max_fd):
try:
val = fcntl(_fd, F_GETFD)
if not val & FD_CLOEXEC:
fcntl(_fd, F_SETFD, val | FD_CLOEXEC)
except OSError:
pass
def cmdline() -> list[str]:
"""Collect path and arguments to re-execute the current hass instance."""
if os.path.basename(sys.argv[0]) == "__main__.py":
modulepath = os.path.dirname(sys.argv[0])
os.environ["PYTHONPATH"] = os.path.dirname(modulepath)
return [sys.executable, "-m", "homeassistant"] + list(sys.argv[1:])
return [sys.executable] + [arg for arg in sys.argv if arg != "--daemon"]
return sys.argv
return [arg for arg in sys.argv if arg != "--daemon"]
def check_threads() -> None:
"""Check if there are any lingering threads."""
def try_to_restart() -> None:
"""Attempt to clean up state and start a new Home Assistant instance."""
# Things should be mostly shut down already at this point, now just try
# to clean up things that may have been left behind.
sys.stderr.write("Home Assistant attempting to restart.\n")
# Count remaining threads, ideally there should only be one non-daemonized
# thread left (which is us). Nothing we really do with it, but it might be
# useful when debugging shutdown/restart issues.
try:
nthreads = sum(
thread.is_alive() and not thread.daemon for thread in threading.enumerate()
@@ -161,27 +240,64 @@ def check_threads() -> None:
except AssertionError:
sys.stderr.write("Failed to count non-daemonic threads.\n")
# Try to not leave behind open filedescriptors with the emphasis on try.
try:
max_fd = os.sysconf("SC_OPEN_MAX")
except ValueError:
max_fd = 256
if platform.system() == "Darwin":
closefds_osx(3, max_fd)
else:
os.closerange(3, max_fd)
# Now launch into a new instance of Home Assistant. If this fails we
# fall through and exit with error 100 (RESTART_EXIT_CODE) in which case
# systemd will restart us when RestartForceExitStatus=100 is set in the
# systemd.service file.
sys.stderr.write("Restarting Home Assistant\n")
args = cmdline()
os.execv(args[0], args)
def main() -> int:
"""Start Home Assistant."""
validate_python()
args = get_arguments()
# Run a simple daemon runner process on Windows to handle restarts
if os.name == "nt" and "--runner" not in sys.argv:
nt_args = cmdline() + ["--runner"]
while True:
try:
subprocess.check_call(nt_args)
sys.exit(0)
except KeyboardInterrupt:
sys.exit(0)
except subprocess.CalledProcessError as exc:
if exc.returncode != RESTART_EXIT_CODE:
sys.exit(exc.returncode)
if not args.ignore_os_check:
validate_os()
args = get_arguments()
if args.script is not None:
# pylint: disable=import-outside-toplevel
from . import scripts
from homeassistant import scripts
return scripts.run(args.script)
config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config))
ensure_config_path(config_dir)
# Daemon functions
if args.pid_file:
check_pid(args.pid_file)
if args.daemon:
daemonize()
if args.pid_file:
write_pid(args.pid_file)
# pylint: disable=import-outside-toplevel
from . import runner
from homeassistant import runner
runtime_conf = runner.RuntimeConfig(
config_dir=config_dir,
@@ -190,22 +306,14 @@ def main() -> int:
log_file=args.log_file,
log_no_color=args.log_no_color,
skip_pip=args.skip_pip,
skip_pip_packages=args.skip_pip_packages,
safe_mode=args.safe_mode,
debug=args.debug,
open_ui=args.open_ui,
)
fault_file_name = os.path.join(config_dir, FAULT_LOG_FILENAME)
with open(fault_file_name, mode="a", encoding="utf8") as fault_file:
faulthandler.enable(fault_file)
exit_code = runner.run(runtime_conf)
faulthandler.disable()
if os.path.getsize(fault_file_name) == 0:
os.remove(fault_file_name)
check_threads()
exit_code = runner.run(runtime_conf)
if exit_code == RESTART_EXIT_CODE and not args.runner:
try_to_restart()
return exit_code
+26 -76
View File
@@ -3,14 +3,13 @@ from __future__ import annotations
import asyncio
from collections import OrderedDict
from collections.abc import Mapping
from datetime import timedelta
from typing import Any, Optional, cast
from typing import Any, Dict, Mapping, Optional, Tuple, cast
import jwt
from homeassistant import data_entry_flow
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.util import dt as dt_util
@@ -20,12 +19,11 @@ from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config
from .providers import AuthProvider, LoginFlow, auth_provider_from_config
EVENT_USER_ADDED = "user_added"
EVENT_USER_UPDATED = "user_updated"
EVENT_USER_REMOVED = "user_removed"
_MfaModuleDict = dict[str, MultiFactorAuthModule]
_ProviderKey = tuple[str, Optional[str]]
_ProviderDict = dict[_ProviderKey, AuthProvider]
_MfaModuleDict = Dict[str, MultiFactorAuthModule]
_ProviderKey = Tuple[str, Optional[str]]
_ProviderDict = Dict[_ProviderKey, AuthProvider]
class InvalidAuthError(Exception):
@@ -87,7 +85,7 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager):
async def async_create_flow(
self,
handler_key: str,
handler_key: Any,
*,
context: dict[str, Any] | None = None,
data: dict[str, Any] | None = None,
@@ -104,7 +102,7 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager):
"""Return a user as result of login flow."""
flow = cast(LoginFlow, flow)
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
return result
# we got final result
@@ -157,7 +155,6 @@ class AuthManager:
self._providers = providers
self._mfa_modules = mfa_modules
self.login_flow = AuthManagerFlowManager(hass, self)
self._revoke_callbacks: dict[str, list[CALLBACK_TYPE]] = {}
@property
def auth_providers(self) -> list[AuthProvider]:
@@ -216,19 +213,11 @@ class AuthManager:
return None
async def async_create_system_user(
self,
name: str,
*,
group_ids: list[str] | None = None,
local_only: bool | None = None,
self, name: str, group_ids: list[str] | None = None
) -> models.User:
"""Create a system user."""
user = await self._store.async_create_user(
name=name,
system_generated=True,
is_active=True,
group_ids=group_ids or [],
local_only=local_only,
name=name, system_generated=True, is_active=True, group_ids=group_ids or []
)
self.hass.bus.async_fire(EVENT_USER_ADDED, {"user_id": user.id})
@@ -236,18 +225,13 @@ class AuthManager:
return user
async def async_create_user(
self,
name: str,
*,
group_ids: list[str] | None = None,
local_only: bool | None = None,
self, name: str, group_ids: list[str] | None = None
) -> models.User:
"""Create a user."""
kwargs: dict[str, Any] = {
"name": name,
"is_active": True,
"group_ids": group_ids or [],
"local_only": local_only,
}
if await self._user_should_be_owner():
@@ -291,12 +275,6 @@ class AuthManager:
self, user: models.User, credentials: models.Credentials
) -> None:
"""Link credentials to an existing user."""
linked_user = await self.async_get_user_by_credentials(credentials)
if linked_user == user:
return
if linked_user is not None:
raise ValueError("Credential is already linked to a user")
await self._store.async_link_user(user, credentials)
async def async_remove_user(self, user: models.User) -> None:
@@ -307,7 +285,7 @@ class AuthManager:
]
if tasks:
await asyncio.gather(*tasks)
await asyncio.wait(tasks)
await self._store.async_remove_user(user)
@@ -319,18 +297,13 @@ class AuthManager:
name: str | None = None,
is_active: bool | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> None:
"""Update a user."""
kwargs: dict[str, Any] = {}
for attr_name, value in (
("name", name),
("group_ids", group_ids),
("local_only", local_only),
):
if value is not None:
kwargs[attr_name] = value
if name is not None:
kwargs["name"] = name
if group_ids is not None:
kwargs["group_ids"] = group_ids
await self._store.async_update_user(user, **kwargs)
if is_active is not None:
@@ -339,8 +312,6 @@ class AuthManager:
else:
await self.async_deactivate_user(user)
self.hass.bus.async_fire(EVENT_USER_UPDATED, {"user_id": user.id})
async def async_activate_user(self, user: models.User) -> None:
"""Activate a user."""
await self._store.async_activate_user(user)
@@ -356,7 +327,8 @@ class AuthManager:
provider = self._async_get_auth_provider(credentials)
if provider is not None and hasattr(provider, "async_will_remove_credentials"):
await provider.async_will_remove_credentials(credentials)
# https://github.com/python/mypy/issues/1424
await provider.async_will_remove_credentials(credentials) # type: ignore
await self._store.async_remove_credentials(credentials)
@@ -369,7 +341,8 @@ class AuthManager:
"System generated users cannot enable multi-factor auth module."
)
if (module := self.get_auth_mfa_module(mfa_module_id)) is None:
module = self.get_auth_mfa_module(mfa_module_id)
if module is None:
raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")
await module.async_setup_user(user.id, data)
@@ -383,7 +356,8 @@ class AuthManager:
"System generated users cannot disable multi-factor auth module."
)
if (module := self.get_auth_mfa_module(mfa_module_id)) is None:
module = self.get_auth_mfa_module(mfa_module_id)
if module is None:
raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")
await module.async_depose_user(user.id)
@@ -474,28 +448,6 @@ class AuthManager:
"""Delete a refresh token."""
await self._store.async_remove_refresh_token(refresh_token)
callbacks = self._revoke_callbacks.pop(refresh_token.id, [])
for revoke_callback in callbacks:
revoke_callback()
@callback
def async_register_revoke_token_callback(
self, refresh_token_id: str, revoke_callback: CALLBACK_TYPE
) -> CALLBACK_TYPE:
"""Register a callback to be called when the refresh token id is revoked."""
if refresh_token_id not in self._revoke_callbacks:
self._revoke_callbacks[refresh_token_id] = []
callbacks = self._revoke_callbacks[refresh_token_id]
callbacks.append(revoke_callback)
@callback
def unregister() -> None:
if revoke_callback in callbacks:
callbacks.remove(revoke_callback)
return unregister
@callback
def async_create_access_token(
self, refresh_token: models.RefreshToken, remote_ip: str | None = None
@@ -514,7 +466,7 @@ class AuthManager:
},
refresh_token.jwt_key,
algorithm="HS256",
)
).decode()
@callback
def _async_resolve_provider(
@@ -534,8 +486,7 @@ class AuthManager:
)
if provider is None:
raise InvalidProvider(
f"Auth provider {refresh_token.credential.auth_provider_type},"
f" {refresh_token.credential.auth_provider_id} not available"
f"Auth provider {refresh_token.credential.auth_provider_type}, {refresh_token.credential.auth_provider_id} not available"
)
return provider
@@ -547,7 +498,8 @@ class AuthManager:
Will raise InvalidAuthError on errors.
"""
if provider := self._async_resolve_provider(refresh_token):
provider = self._async_resolve_provider(refresh_token)
if provider:
provider.async_validate_refresh_token(refresh_token, remote_ip)
async def async_validate_access_token(
@@ -555,9 +507,7 @@ class AuthManager:
) -> models.RefreshToken | None:
"""Return refresh token if an access token is valid."""
try:
unverif_claims = jwt.decode(
token, algorithms=["HS256"], options={"verify_signature": False}
)
unverif_claims = jwt.decode(token, verify=False)
except jwt.InvalidTokenError:
return None
+33 -44
View File
@@ -8,22 +8,17 @@ import hmac
from logging import getLogger
from typing import Any
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.storage import Store
from homeassistant.util import dt as dt_util
from . import models
from .const import (
ACCESS_TOKEN_EXPIRATION,
GROUP_ID_ADMIN,
GROUP_ID_READ_ONLY,
GROUP_ID_USER,
)
from .permissions import system_policies
from .permissions.models import PermissionLookup
from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER
from .permissions import PermissionLookup, system_policies
from .permissions.types import PolicyType
# mypy: disallow-any-generics
STORAGE_VERSION = 1
STORAGE_KEY = "auth"
GROUP_NAME_ADMIN = "Administrators"
@@ -46,8 +41,8 @@ class AuthStore:
self._users: dict[str, models.User] | None = None
self._groups: dict[str, models.Group] | None = None
self._perm_lookup: PermissionLookup | None = None
self._store = Store[dict[str, list[dict[str, Any]]]](
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True
)
self._lock = asyncio.Lock()
@@ -91,7 +86,6 @@ class AuthStore:
system_generated: bool | None = None,
credentials: models.Credentials | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> models.User:
"""Create a new user."""
if self._users is None:
@@ -102,7 +96,8 @@ class AuthStore:
groups = []
for group_id in group_ids or []:
if (group := self._groups.get(group_id)) is None:
group = self._groups.get(group_id)
if group is None:
raise ValueError(f"Invalid group specified {group_id}")
groups.append(group)
@@ -114,14 +109,14 @@ class AuthStore:
"perm_lookup": self._perm_lookup,
}
for attr_name, value in (
("is_owner", is_owner),
("is_active", is_active),
("local_only", local_only),
("system_generated", system_generated),
):
if value is not None:
kwargs[attr_name] = value
if is_owner is not None:
kwargs["is_owner"] = is_owner
if is_active is not None:
kwargs["is_active"] = is_active
if system_generated is not None:
kwargs["system_generated"] = system_generated
new_user = models.User(**kwargs)
@@ -158,7 +153,6 @@ class AuthStore:
name: str | None = None,
is_active: bool | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> None:
"""Update a user."""
assert self._groups is not None
@@ -166,18 +160,15 @@ class AuthStore:
if group_ids is not None:
groups = []
for grid in group_ids:
if (group := self._groups.get(grid)) is None:
group = self._groups.get(grid)
if group is None:
raise ValueError("Invalid group specified.")
groups.append(group)
user.groups = groups
user.invalidate_permission_cache()
for attr_name, value in (
("name", name),
("is_active", is_active),
("local_only", local_only),
):
for attr_name, value in (("name", name), ("is_active", is_active)):
if value is not None:
setattr(user, attr_name, value)
@@ -305,9 +296,11 @@ class AuthStore:
async def _async_load_task(self) -> None:
"""Load the users."""
dev_reg = dr.async_get(self.hass)
ent_reg = er.async_get(self.hass)
data = await self._store.async_load()
[ent_reg, dev_reg, data] = await asyncio.gather(
self.hass.helpers.entity_registry.async_get_registry(),
self.hass.helpers.device_registry.async_get_registry(),
self._store.async_load(),
)
# Make sure that we're not overriding data if 2 loads happened at the
# same time
@@ -316,7 +309,7 @@ class AuthStore:
self._perm_lookup = perm_lookup = PermissionLookup(ent_reg, dev_reg)
if data is None or not isinstance(data, dict):
if data is None:
self._set_defaults()
return
@@ -426,8 +419,6 @@ class AuthStore:
is_active=user_dict["is_active"],
system_generated=user_dict["system_generated"],
perm_lookup=perm_lookup,
# New in 2021.11
local_only=user_dict.get("local_only", False),
)
for cred_dict in data["credentials"]:
@@ -449,22 +440,22 @@ class AuthStore:
created_at = dt_util.parse_datetime(rt_dict["created_at"])
if created_at is None:
getLogger(__name__).error(
(
"Ignoring refresh token %(id)s with invalid created_at "
"%(created_at)s for user_id %(user_id)s"
),
"Ignoring refresh token %(id)s with invalid created_at "
"%(created_at)s for user_id %(user_id)s",
rt_dict,
)
continue
if (token_type := rt_dict.get("token_type")) is None:
token_type = rt_dict.get("token_type")
if token_type is None:
if rt_dict["client_id"] is None:
token_type = models.TOKEN_TYPE_SYSTEM
else:
token_type = models.TOKEN_TYPE_NORMAL
# old refresh_token don't have last_used_at (pre-0.78)
if last_used_at_str := rt_dict.get("last_used_at"):
last_used_at_str = rt_dict.get("last_used_at")
if last_used_at_str:
last_used_at = dt_util.parse_datetime(last_used_at_str)
else:
last_used_at = None
@@ -485,10 +476,9 @@ class AuthStore:
jwt_key=rt_dict["jwt_key"],
last_used_at=last_used_at,
last_used_ip=rt_dict.get("last_used_ip"),
credential=credentials.get(rt_dict.get("credential_id")),
version=rt_dict.get("version"),
)
if "credential_id" in rt_dict:
token.credential = credentials.get(rt_dict["credential_id"])
users[rt_dict["user_id"]].refresh_tokens[token.id] = token
self._groups = groups
@@ -516,7 +506,6 @@ class AuthStore:
"is_active": user.is_active,
"name": user.name,
"system_generated": user.system_generated,
"local_only": user.local_only,
}
for user in self._users.values()
]
+10 -7
View File
@@ -16,7 +16,7 @@ from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.decorator import Registry
MULTI_FACTOR_AUTH_MODULES: Registry[str, type[MultiFactorAuthModule]] = Registry()
MULTI_FACTOR_AUTH_MODULES = Registry()
MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema(
{
@@ -55,7 +55,7 @@ class MultiFactorAuthModule:
@property
def type(self) -> str:
"""Return type of the module."""
return self.config[CONF_TYPE] # type: ignore[no-any-return]
return self.config[CONF_TYPE] # type: ignore
@property
def name(self) -> str:
@@ -116,7 +116,9 @@ class SetupFlow(data_entry_flow.FlowHandler):
if user_input:
result = await self._auth_module.async_setup_user(self._user_id, user_input)
return self.async_create_entry(data={"result": result})
return self.async_create_entry(
title=self._auth_module.name, data={"result": result}
)
return self.async_show_form(
step_id="init", data_schema=self._setup_schema, errors=errors
@@ -127,11 +129,11 @@ async def auth_mfa_module_from_config(
hass: HomeAssistant, config: dict[str, Any]
) -> MultiFactorAuthModule:
"""Initialize an auth module from a config."""
module_name: str = config[CONF_TYPE]
module_name = config[CONF_TYPE]
module = await _load_mfa_module(hass, module_name)
try:
config = module.CONFIG_SCHEMA(config)
config = module.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as err:
_LOGGER.error(
"Invalid configuration for multi-factor module %s: %s",
@@ -140,7 +142,7 @@ async def auth_mfa_module_from_config(
)
raise
return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config)
return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) # type: ignore
async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType:
@@ -164,8 +166,9 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.Modul
processed = hass.data[DATA_REQS] = set()
# https://github.com/python/mypy/issues/1424
await requirements.async_process_requirements(
hass, module_path, module.REQUIREMENTS
hass, module_path, module.REQUIREMENTS # type: ignore
)
processed.add(module_name)
@@ -38,12 +38,12 @@ class InsecureExampleModule(MultiFactorAuthModule):
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({vol.Required("pin"): str})
return vol.Schema({"pin": str})
@property
def setup_schema(self) -> vol.Schema:
"""Validate async_setup_user input data."""
return vol.Schema({vol.Required("pin"): str})
return vol.Schema({"pin": str})
async def async_setup_flow(self, user_id: str) -> SetupFlow:
"""Return a data entry flow handler for setup module.
+21 -17
View File
@@ -7,7 +7,7 @@ from __future__ import annotations
import asyncio
from collections import OrderedDict
import logging
from typing import Any, cast
from typing import Any, Dict
import attr
import voluptuous as vol
@@ -17,7 +17,6 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import ServiceNotFound
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.storage import Store
from . import (
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
@@ -26,7 +25,7 @@ from . import (
SetupFlow,
)
REQUIREMENTS = ["pyotp==2.8.0"]
REQUIREMENTS = ["pyotp==2.3.0"]
CONF_MESSAGE = "message"
@@ -57,10 +56,10 @@ def _generate_secret() -> str:
def _generate_random() -> int:
"""Generate a 32 digit number."""
"""Generate a 8 digit number."""
import pyotp # pylint: disable=import-outside-toplevel
return int(pyotp.random_base32(length=32, chars=list("1234567890")))
return int(pyotp.random_base32(length=8, chars=list("1234567890")))
def _generate_otp(secret: str, count: int) -> str:
@@ -87,7 +86,7 @@ class NotifySetting:
target: str | None = attr.ib(default=None)
_UsersDict = dict[str, NotifySetting]
_UsersDict = Dict[str, NotifySetting]
@MULTI_FACTOR_AUTH_MODULES.register("notify")
@@ -100,8 +99,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
"""Initialize the user data store."""
super().__init__(hass, config)
self._user_settings: _UsersDict | None = None
self._user_store = Store[dict[str, dict[str, Any]]](
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True
)
self._include = config.get(CONF_INCLUDE, [])
self._exclude = config.get(CONF_EXCLUDE, [])
@@ -111,7 +110,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({vol.Required(INPUT_FIELD_CODE): str})
return vol.Schema({INPUT_FIELD_CODE: str})
async def _async_load(self) -> None:
"""Load stored data."""
@@ -119,8 +118,10 @@ class NotifyAuthModule(MultiFactorAuthModule):
if self._user_settings is not None:
return
if (data := await self._user_store.async_load()) is None:
data = cast(dict[str, dict[str, Any]], {STORAGE_USERS: {}})
data = await self._user_store.async_load()
if data is None:
data = {STORAGE_USERS: {}}
self._user_settings = {
user_id: NotifySetting(**setting)
@@ -206,7 +207,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
await self._async_load()
assert self._user_settings is not None
if (notify_setting := self._user_settings.get(user_id)) is None:
notify_setting = self._user_settings.get(user_id)
if notify_setting is None:
return False
# user_input has been validate in caller
@@ -223,7 +225,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
await self._async_load()
assert self._user_settings is not None
if (notify_setting := self._user_settings.get(user_id)) is None:
notify_setting = self._user_settings.get(user_id)
if notify_setting is None:
raise ValueError("Cannot find user_id")
def generate_secret_and_one_time_password() -> str:
@@ -246,13 +249,14 @@ class NotifyAuthModule(MultiFactorAuthModule):
await self._async_load()
assert self._user_settings is not None
if (notify_setting := self._user_settings.get(user_id)) is None:
notify_setting = self._user_settings.get(user_id)
if notify_setting is None:
_LOGGER.error("Cannot find user %s", user_id)
return
await self.async_notify(
code,
notify_setting.notify_service, # type: ignore[arg-type]
notify_setting.notify_service, # type: ignore
notify_setting.target,
)
@@ -320,7 +324,6 @@ class NotifySetupFlow(SetupFlow):
errors: dict[str, str] = {}
hass = self._auth_module.hass
assert self._secret and self._count
if user_input:
verified = await hass.async_add_executor_job(
_verify_otp, self._secret, user_input["code"], self._count
@@ -330,11 +333,12 @@ class NotifySetupFlow(SetupFlow):
self._user_id,
{"notify_service": self._notify_service, "target": self._target},
)
return self.async_create_entry(data={})
return self.async_create_entry(title=self._auth_module.name, data={})
errors["base"] = "invalid_code"
# generate code every time, no retry logic
assert self._secret and self._count
code = await hass.async_add_executor_job(
_generate_otp, self._secret, self._count
)
+24 -22
View File
@@ -3,14 +3,13 @@ from __future__ import annotations
import asyncio
from io import BytesIO
from typing import Any, cast
from typing import Any
import voluptuous as vol
from homeassistant.auth.models import User
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.storage import Store
from . import (
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
@@ -19,7 +18,7 @@ from . import (
SetupFlow,
)
REQUIREMENTS = ["pyotp==2.8.0", "PyQRCode==1.2.1"]
REQUIREMENTS = ["pyotp==2.3.0", "PyQRCode==1.2.1"]
CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA)
@@ -47,10 +46,8 @@ def _generate_qr_code(data: str) -> str:
.decode("ascii")
.replace("\n", "")
.replace(
(
'<?xml version="1.0" encoding="UTF-8"?>'
'<svg xmlns="http://www.w3.org/2000/svg"'
),
'<?xml version="1.0" encoding="UTF-8"?>'
'<svg xmlns="http://www.w3.org/2000/svg"',
"<svg",
)
)
@@ -79,15 +76,15 @@ class TotpAuthModule(MultiFactorAuthModule):
"""Initialize the user data store."""
super().__init__(hass, config)
self._users: dict[str, str] | None = None
self._user_store = Store[dict[str, dict[str, str]]](
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True
)
self._init_lock = asyncio.Lock()
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({vol.Required(INPUT_FIELD_CODE): str})
return vol.Schema({INPUT_FIELD_CODE: str})
async def _async_load(self) -> None:
"""Load stored data."""
@@ -95,14 +92,16 @@ class TotpAuthModule(MultiFactorAuthModule):
if self._users is not None:
return
if (data := await self._user_store.async_load()) is None:
data = cast(dict[str, dict[str, str]], {STORAGE_USERS: {}})
data = await self._user_store.async_load()
if data is None:
data = {STORAGE_USERS: {}}
self._users = data.get(STORAGE_USERS, {})
async def _async_save(self) -> None:
"""Save data."""
await self._user_store.async_save({STORAGE_USERS: self._users or {}})
await self._user_store.async_save({STORAGE_USERS: self._users})
def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str:
"""Create a ota_secret for user."""
@@ -110,7 +109,7 @@ class TotpAuthModule(MultiFactorAuthModule):
ota_secret: str = secret or pyotp.random_base32()
self._users[user_id] = ota_secret # type: ignore[index]
self._users[user_id] = ota_secret # type: ignore
return ota_secret
async def async_setup_flow(self, user_id: str) -> SetupFlow:
@@ -139,7 +138,7 @@ class TotpAuthModule(MultiFactorAuthModule):
if self._users is None:
await self._async_load()
if self._users.pop(user_id, None): # type: ignore[union-attr]
if self._users.pop(user_id, None): # type: ignore
await self._async_save()
async def async_is_user_setup(self, user_id: str) -> bool:
@@ -147,7 +146,7 @@ class TotpAuthModule(MultiFactorAuthModule):
if self._users is None:
await self._async_load()
return user_id in self._users # type: ignore[operator]
return user_id in self._users # type: ignore
async def async_validate(self, user_id: str, user_input: dict[str, Any]) -> bool:
"""Return True if validation passed."""
@@ -164,7 +163,8 @@ class TotpAuthModule(MultiFactorAuthModule):
"""Validate two factor authentication code."""
import pyotp # pylint: disable=import-outside-toplevel
if (ota_secret := self._users.get(user_id)) is None: # type: ignore[union-attr]
ota_secret = self._users.get(user_id) # type: ignore
if ota_secret is None:
# even we cannot find user, we still do verify
# to make timing the same as if user was found.
pyotp.TOTP(DUMMY_SECRET).verify(code, valid_window=1)
@@ -184,9 +184,9 @@ class TotpSetupFlow(SetupFlow):
# to fix typing complaint
self._auth_module: TotpAuthModule = auth_module
self._user = user
self._ota_secret: str = ""
self._url: str | None = None
self._image: str | None = None
self._ota_secret: str | None = None
self._url = None # type Optional[str]
self._image = None # type Optional[str]
async def async_step_init(
self, user_input: dict[str, str] | None = None
@@ -208,7 +208,9 @@ class TotpSetupFlow(SetupFlow):
result = await self._auth_module.async_setup_user(
self._user_id, {"secret": self._ota_secret}
)
return self.async_create_entry(data={"result": result})
return self.async_create_entry(
title=self._auth_module.name, data={"result": result}
)
errors["base"] = "invalid_code"
@@ -219,7 +221,7 @@ class TotpSetupFlow(SetupFlow):
self._url,
self._image,
) = await hass.async_add_executor_job(
_generate_secret_and_qr_code,
_generate_secret_and_qr_code, # type: ignore
str(self._user.name),
)
-1
View File
@@ -39,7 +39,6 @@ class User:
is_owner: bool = attr.ib(default=False)
is_active: bool = attr.ib(default=False)
system_generated: bool = attr.ib(default=False)
local_only: bool = attr.ib(default=False)
groups: list[Group] = attr.ib(factory=list, eq=False, order=False)
+7 -13
View File
@@ -1,29 +1,21 @@
"""Permissions for Home Assistant."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
import logging
from typing import Any, Callable
import voluptuous as vol
from .const import CAT_ENTITIES
from .entities import ENTITY_POLICY_SCHEMA, compile_entities
from .merge import merge_policies
from .merge import merge_policies # noqa: F401
from .models import PermissionLookup
from .types import PolicyType
from .util import test_all
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
__all__ = [
"POLICY_SCHEMA",
"merge_policies",
"PermissionLookup",
"PolicyType",
"AbstractPermissions",
"PolicyPermissions",
"OwnerPermissions",
]
_LOGGER = logging.getLogger(__name__)
class AbstractPermissions:
@@ -41,7 +33,9 @@ class AbstractPermissions:
def check_entity(self, entity_id: str, key: str) -> bool:
"""Check if we can access entity."""
if (entity_func := self._cached_entity_func) is None:
entity_func = self._cached_entity_func
if entity_func is None:
entity_func = self._cached_entity_func = self._entity_func()
return entity_func(entity_id, key)
+2 -2
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from collections import OrderedDict
from collections.abc import Callable
from typing import Callable
import voluptuous as vol
@@ -47,7 +47,7 @@ def _lookup_domain(
perm_lookup: PermissionLookup, domains_dict: SubCategoryDict, entity_id: str
) -> ValueType | None:
"""Look up entity permissions by domain."""
return domains_dict.get(entity_id.partition(".")[0])
return domains_dict.get(entity_id.split(".", 1)[0])
def _lookup_area(
+1 -2
View File
@@ -1,6 +1,5 @@
"""Common code for permissions."""
from collections.abc import Mapping
from typing import Union
from typing import Mapping, Union
# MyPy doesn't support recursion yet. So writing it out as far as we need.
+4 -4
View File
@@ -1,16 +1,15 @@
"""Helpers to deal with permissions."""
from __future__ import annotations
from collections.abc import Callable
from functools import wraps
from typing import Optional, cast
from typing import Callable, Dict, Optional, cast
from .const import SUBCAT_ALL
from .models import PermissionLookup
from .types import CategoryType, SubCategoryDict, ValueType
LookupFunc = Callable[[PermissionLookup, SubCategoryDict, str], Optional[ValueType]]
SubCatLookupType = dict[str, LookupFunc]
SubCatLookupType = Dict[str, LookupFunc]
def lookup_all(
@@ -73,7 +72,8 @@ def compile_policy(
def apply_policy_funcs(object_id: str, key: str) -> bool:
"""Apply several policy functions."""
for func in funcs:
if (result := func(object_id, key)) is not None:
result = func(object_id, key)
if result is not None:
return result
return False
+17 -10
View File
@@ -22,10 +22,12 @@ from ..auth_store import AuthStore
from ..const import MFA_SESSION_EXPIRATION
from ..models import Credentials, RefreshToken, User, UserMeta
# mypy: disallow-any-generics
_LOGGER = logging.getLogger(__name__)
DATA_REQS = "auth_prov_reqs_processed"
AUTH_PROVIDERS: Registry[str, type[AuthProvider]] = Registry()
AUTH_PROVIDERS = Registry()
AUTH_PROVIDER_SCHEMA = vol.Schema(
{
@@ -62,7 +64,7 @@ class AuthProvider:
@property
def type(self) -> str:
"""Return type of the provider."""
return self.config[CONF_TYPE] # type: ignore[no-any-return]
return self.config[CONF_TYPE] # type: ignore
@property
def name(self) -> str:
@@ -136,11 +138,11 @@ async def auth_provider_from_config(
hass: HomeAssistant, store: AuthStore, config: dict[str, Any]
) -> AuthProvider:
"""Initialize an auth provider from a config."""
provider_name: str = config[CONF_TYPE]
provider_name = config[CONF_TYPE]
module = await load_auth_provider_module(hass, provider_name)
try:
config = module.CONFIG_SCHEMA(config)
config = module.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as err:
_LOGGER.error(
"Invalid configuration for auth provider %s: %s",
@@ -149,7 +151,7 @@ async def auth_provider_from_config(
)
raise
return AUTH_PROVIDERS[provider_name](hass, store, config)
return AUTH_PROVIDERS[provider_name](hass, store, config) # type: ignore
async def load_auth_provider_module(
@@ -167,12 +169,15 @@ async def load_auth_provider_module(
if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"):
return module
if (processed := hass.data.get(DATA_REQS)) is None:
processed = hass.data.get(DATA_REQS)
if processed is None:
processed = hass.data[DATA_REQS] = set()
elif provider in processed:
return module
reqs = module.REQUIREMENTS
# https://github.com/python/mypy/issues/1424
reqs = module.REQUIREMENTS # type: ignore
await requirements.async_process_requirements(
hass, f"auth provider {provider}", reqs
)
@@ -250,7 +255,9 @@ class LoginFlow(data_entry_flow.FlowHandler):
auth_module, "async_initialize_login_mfa_step"
):
try:
await auth_module.async_initialize_login_mfa_step(self.user.id)
await auth_module.async_initialize_login_mfa_step( # type: ignore
self.user.id
)
except HomeAssistantError:
_LOGGER.exception("Error initializing MFA step")
return self.async_abort(reason="unknown_error")
@@ -270,7 +277,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
if not errors:
return await self.async_finish(self.credential)
description_placeholders: dict[str, str] = {
description_placeholders: dict[str, str | None] = {
"mfa_module_name": auth_module.name,
"mfa_module_id": auth_module.id,
}
@@ -284,4 +291,4 @@ class LoginFlow(data_entry_flow.FlowHandler):
async def async_finish(self, flow_result: Any) -> FlowResult:
"""Handle the pass of login flow."""
return self.async_create_entry(data=flow_result)
return self.async_create_entry(title=self._auth_provider.name, data=flow_result)
+11 -11
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
import collections
from collections.abc import Mapping
import logging
import os
@@ -16,6 +17,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
CONF_ARGS = "args"
CONF_META = "meta"
@@ -88,12 +91,12 @@ class CommandLineAuthProvider(AuthProvider):
for _line in stdout.splitlines():
try:
line = _line.decode().lstrip()
if line.startswith("#"):
continue
key, value = line.split("=", 1)
except ValueError:
# malformed line
continue
if line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
key = key.strip()
value = value.strip()
if key in self.ALLOWED_META_KEYS:
@@ -145,13 +148,10 @@ class CommandLineLoginFlow(LoginFlow):
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = collections.OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
step_id="init", data_schema=vol.Schema(schema), errors=errors
)
+29 -30
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio
import base64
from collections import OrderedDict
from collections.abc import Mapping
import logging
from typing import Any, cast
@@ -14,11 +15,12 @@ from homeassistant.const import CONF_ID
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.storage import Store
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
STORAGE_VERSION = 1
STORAGE_KEY = "auth_provider.homeassistant"
@@ -61,10 +63,10 @@ class Data:
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the user data store."""
self.hass = hass
self._store = Store[dict[str, list[dict[str, str]]]](
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True
)
self._data: dict[str, list[dict[str, str]]] | None = None
self._data: dict[str, Any] | None = None
# Legacy mode will allow usernames to start/end with whitespace
# and will compare usernames case-insensitive.
# Remove in 2020 or when we launch 1.0.
@@ -80,8 +82,10 @@ class Data:
async def async_load(self) -> None:
"""Load stored data."""
if (data := await self._store.async_load()) is None:
data = cast(dict[str, list[dict[str, str]]], {"users": []})
data = await self._store.async_load()
if data is None:
data = {"users": []}
seen: set[str] = set()
@@ -89,15 +93,15 @@ class Data:
username = user["username"]
# check if we have duplicates
if (folded := username.casefold()) in seen:
folded = username.casefold()
if folded in seen:
self.is_legacy = True
logging.getLogger(__name__).warning(
(
"Home Assistant auth provider is running in legacy mode "
"because we detected usernames that are case-insensitive"
"equivalent. Please change the username: '%s'."
),
"Home Assistant auth provider is running in legacy mode "
"because we detected usernames that are case-insensitive"
"equivalent. Please change the username: '%s'.",
username,
)
@@ -110,11 +114,9 @@ class Data:
self.is_legacy = True
logging.getLogger(__name__).warning(
(
"Home Assistant auth provider is running in legacy mode "
"because we detected usernames that start or end in a "
"space. Please change the username: '%s'."
),
"Home Assistant auth provider is running in legacy mode "
"because we detected usernames that start or end in a "
"space. Please change the username: '%s'.",
username,
)
@@ -125,8 +127,7 @@ class Data:
@property
def users(self) -> list[dict[str, str]]:
"""Return users."""
assert self._data is not None
return self._data["users"]
return self._data["users"] # type: ignore
def validate_login(self, username: str, password: str) -> None:
"""Validate a username and password.
@@ -153,7 +154,9 @@ class Data:
if not bcrypt.checkpw(password.encode(), user_hash):
raise InvalidAuth
def hash_password(self, password: str, for_storage: bool = False) -> bytes:
def hash_password( # pylint: disable=no-self-use
self, password: str, for_storage: bool = False
) -> bytes:
"""Encode a password."""
hashed: bytes = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
@@ -209,8 +212,7 @@ class Data:
async def async_save(self) -> None:
"""Save data."""
if self._data is not None:
await self._store.async_save(self._data)
await self._store.async_save(self._data)
@AUTH_PROVIDERS.register("homeassistant")
@@ -337,13 +339,10 @@ class HassLoginFlow(LoginFlow):
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
step_id="init", data_schema=vol.Schema(schema), errors=errors
)
@@ -1,6 +1,7 @@
"""Example auth provider."""
from __future__ import annotations
from collections import OrderedDict
from collections.abc import Mapping
import hmac
from typing import Any, cast
@@ -14,6 +15,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
USER_SCHEMA = vol.Schema(
{
vol.Required("username"): str,
@@ -100,7 +103,7 @@ class ExampleLoginFlow(LoginFlow):
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the step of the form."""
errors = None
errors = {}
if user_input is not None:
try:
@@ -108,19 +111,16 @@ class ExampleLoginFlow(LoginFlow):
user_input["username"], user_input["password"]
)
except InvalidAuthError:
errors = {"base": "invalid_auth"}
errors["base"] = "invalid_auth"
if not errors:
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
step_id="init", data_schema=vol.Schema(schema), errors=errors
)
@@ -19,6 +19,8 @@ import homeassistant.helpers.config_validation as cv
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
AUTH_PROVIDER_TYPE = "legacy_api_password"
CONF_API_PASSWORD = "api_password"
@@ -100,7 +102,5 @@ class LegacyLoginFlow(LoginFlow):
return await self.async_finish({})
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({vol.Required("password"): str}),
errors=errors,
step_id="init", data_schema=vol.Schema({"password": str}), errors=errors
)
@@ -14,7 +14,7 @@ from ipaddress import (
ip_address,
ip_network,
)
from typing import Any, Union, cast
from typing import Any, Dict, List, Union, cast
import voluptuous as vol
@@ -27,6 +27,8 @@ from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from .. import InvalidAuthError
from ..models import Credentials, RefreshToken, UserMeta
# mypy: disallow-any-generics
IPAddress = Union[IPv4Address, IPv6Address]
IPNetwork = Union[IPv4Network, IPv6Network]
@@ -74,12 +76,12 @@ class TrustedNetworksAuthProvider(AuthProvider):
@property
def trusted_networks(self) -> list[IPNetwork]:
"""Return trusted networks."""
return cast(list[IPNetwork], self.config[CONF_TRUSTED_NETWORKS])
return cast(List[IPNetwork], self.config[CONF_TRUSTED_NETWORKS])
@property
def trusted_users(self) -> dict[IPNetwork, Any]:
"""Return trusted users per network."""
return cast(dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS])
return cast(Dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS])
@property
def trusted_proxies(self) -> list[IPNetwork]:
@@ -192,12 +194,6 @@ class TrustedNetworksAuthProvider(AuthProvider):
if any(ip_addr in trusted_proxy for trusted_proxy in self.trusted_proxies):
raise InvalidAuthError("Can't allow access from a proxy server")
if "cloud" in self.hass.config.components:
from hass_nabucasa import remote # pylint: disable=import-outside-toplevel
if remote.is_cloud_request.get():
raise InvalidAuthError("Can't allow access from Home Assistant Cloud")
@callback
def async_validate_refresh_token(
self, refresh_token: RefreshToken, remote_ip: str | None = None
@@ -248,7 +244,5 @@ class TrustedNetworksLoginFlow(LoginFlow):
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{vol.Required("user"): vol.In(self._available_users)}
),
data_schema=vol.Schema({"user": vol.In(self._available_users)}),
)
-1
View File
@@ -1 +0,0 @@
"""Backports from newer Python versions."""
-35
View File
@@ -1,35 +0,0 @@
"""Enum backports from standard lib."""
from __future__ import annotations
from enum import Enum
from typing import Any, TypeVar
_StrEnumSelfT = TypeVar("_StrEnumSelfT", bound="StrEnum")
class StrEnum(str, Enum):
"""Partial backport of Python 3.11's StrEnum for our basic use cases."""
def __new__(
cls: type[_StrEnumSelfT], value: str, *args: Any, **kwargs: Any
) -> _StrEnumSelfT:
"""Create a new StrEnum instance."""
if not isinstance(value, str):
raise TypeError(f"{value!r} is not a string")
return super().__new__(cls, value, *args, **kwargs)
def __str__(self) -> str:
"""Return self.value."""
return str(self.value)
@staticmethod
def _generate_next_value_(
name: str, start: int, count: int, last_values: list[Any]
) -> Any:
"""
Make `auto()` explicitly unsupported.
We may revisit this when it's very clear that Python 3.11's
`StrEnum.auto()` behavior will no longer change.
"""
raise TypeError("auto() is not supported by this implementation")
+4 -8
View File
@@ -1,17 +1,13 @@
"""Block blocking calls being done in asyncio."""
"""Block I/O being done in asyncio."""
from http.client import HTTPConnection
import time
from .util.async_ import protect_loop
from homeassistant.util.async_ import protect_loop
def enable() -> None:
"""Enable the detection of blocking calls in the event loop."""
"""Enable the detection of I/O in the event loop."""
# Prevent urllib3 and requests doing I/O in event loop
HTTPConnection.putrequest = protect_loop(HTTPConnection.putrequest) # type: ignore[assignment]
# Prevent sleeping in event loop. Non-strict since 2022.02
time.sleep = protect_loop(time.sleep, strict=False)
HTTPConnection.putrequest = protect_loop(HTTPConnection.putrequest) # type: ignore
# Currently disabled. pytz doing I/O when getting timezone.
# Prevent files being opened inside the event loop
+84 -158
View File
@@ -3,11 +3,10 @@ from __future__ import annotations
import asyncio
import contextlib
from datetime import datetime, timedelta
from datetime import datetime
import logging
import logging.handlers
import os
import platform
import sys
import threading
from time import monotonic
@@ -16,33 +15,24 @@ from typing import TYPE_CHECKING, Any
import voluptuous as vol
import yarl
from . import config as conf_util, config_entries, core, loader
from .components import http
from .const import (
REQUIRED_NEXT_PYTHON_HA_RELEASE,
REQUIRED_NEXT_PYTHON_VER,
SIGNAL_BOOTSTRAP_INTEGRATIONS,
)
from .exceptions import HomeAssistantError
from .helpers import (
area_registry,
device_registry,
entity_registry,
issue_registry,
recorder,
)
from .helpers.dispatcher import async_dispatcher_send
from .helpers.typing import ConfigType
from .setup import (
from homeassistant import config as conf_util, config_entries, core, loader
from homeassistant.components import http
from homeassistant.const import REQUIRED_NEXT_PYTHON_DATE, REQUIRED_NEXT_PYTHON_VER
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import area_registry, device_registry, entity_registry
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import (
DATA_SETUP,
DATA_SETUP_STARTED,
DATA_SETUP_TIME,
async_set_domains_to_be_loaded,
async_setup_component,
)
from .util import dt as dt_util
from .util.logging import async_activate_log_queue_handler
from .util.package import async_get_user_site, is_virtual_env
from homeassistant.util.async_ import gather_with_concurrency
import homeassistant.util.dt as dt_util
from homeassistant.util.logging import async_activate_log_queue_handler
from homeassistant.util.package import async_get_user_site, is_virtual_env
if TYPE_CHECKING:
from .runner import RuntimeConfig
@@ -53,10 +43,10 @@ ERROR_LOG_FILENAME = "home-assistant.log"
# hass.data key for logging information.
DATA_LOGGING = "logging"
DATA_REGISTRIES_LOADED = "bootstrap_registries_loaded"
LOG_SLOW_STARTUP_INTERVAL = 60
SLOW_STARTUP_CHECK_INTERVAL = 1
SIGNAL_BOOTSTRAP_INTEGRATONS = "bootstrap_integrations"
STAGE_1_TIMEOUT = 120
STAGE_2_TIMEOUT = 300
@@ -66,39 +56,27 @@ COOLDOWN_TIME = 60
MAX_LOAD_CONCURRENTLY = 6
DEBUGGER_INTEGRATIONS = {"debugpy"}
CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"}
CORE_INTEGRATIONS = ("homeassistant", "persistent_notification")
LOGGING_INTEGRATIONS = {
# Set log levels
"logger",
# Error logging
"system_log",
"sentry",
}
FRONTEND_INTEGRATIONS = {
# Get the frontend up and running as soon as possible so problem
# integrations can be removed and database migration status is
# visible in frontend
"frontend",
}
RECORDER_INTEGRATIONS = {
# Setup after frontend
# To record data
"recorder",
}
DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb", "zeroconf")
STAGE_1_INTEGRATIONS = {
# We need to make sure discovery integrations
# update their deps before stage 2 integrations
# load them inadvertently before their deps have
# been updated which leads to using an old version
# of the dep, or worse (import errors).
*DISCOVERY_INTEGRATIONS,
# To make sure we forward data to other instances
"mqtt_eventstream",
# To provide account link implementations
"cloud",
# Ensure supervisor is available
"hassio",
# Get the frontend up and running as soon
# as possible so problem integrations can
# be removed
"frontend",
}
@@ -118,8 +96,7 @@ async def async_setup_hass(
)
hass.config.skip_pip = runtime_config.skip_pip
hass.config.skip_pip_packages = runtime_config.skip_pip_packages
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
if runtime_config.skip_pip:
_LOGGER.warning(
"Skipping pip installation of required modules. This may cause issues"
)
@@ -132,8 +109,9 @@ async def async_setup_hass(
config_dict = None
basic_setup_success = False
safe_mode = runtime_config.safe_mode
if not (safe_mode := runtime_config.safe_mode):
if not safe_mode:
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
try:
@@ -171,13 +149,9 @@ async def async_setup_hass(
safe_mode = True
old_config = hass.config
old_logging = hass.data.get(DATA_LOGGING)
hass = core.HomeAssistant()
if old_logging:
hass.data[DATA_LOGGING] = old_logging
hass.config.skip_pip = old_config.skip_pip
hass.config.skip_pip_packages = old_config.skip_pip_packages
hass.config.internal_url = old_config.internal_url
hass.config.external_url = old_config.external_url
hass.config.config_dir = old_config.config_dir
@@ -219,32 +193,6 @@ def open_hass_ui(hass: core.HomeAssistant) -> None:
)
async def load_registries(hass: core.HomeAssistant) -> None:
"""Load the registries and cache the result of platform.uname().processor."""
if DATA_REGISTRIES_LOADED in hass.data:
return
hass.data[DATA_REGISTRIES_LOADED] = None
def _cache_uname_processor() -> None:
"""Cache the result of platform.uname().processor in the executor.
Multiple modules call this function at startup which
executes a blocking subprocess call. This is a problem for the
asyncio event loop. By primeing the cache of uname we can
avoid the blocking call in the event loop.
"""
platform.uname().processor # pylint: disable=expression-not-assigned
# Load the registries and cache the result of platform.uname().processor
await asyncio.gather(
area_registry.async_load(hass),
device_registry.async_load(hass),
entity_registry.async_load(hass),
issue_registry.async_load(hass),
hass.async_add_executor_job(_cache_uname_processor),
)
async def async_from_config_dict(
config: ConfigType, hass: core.HomeAssistant
) -> core.HomeAssistant | None:
@@ -257,7 +205,6 @@ async def async_from_config_dict(
hass.config_entries = config_entries.ConfigEntries(hass, config)
await hass.config_entries.async_initialize()
await load_registries(hass)
# Set up core.
_LOGGER.debug("Setting up %s", CORE_INTEGRATIONS)
@@ -284,7 +231,8 @@ async def async_from_config_dict(
return None
except HomeAssistantError:
_LOGGER.error(
"Home Assistant core failed to initialize. Further initialization aborted"
"Home Assistant core failed to initialize. "
"Further initialization aborted"
)
return None
@@ -293,35 +241,18 @@ async def async_from_config_dict(
stop = monotonic()
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
if (
REQUIRED_NEXT_PYTHON_HA_RELEASE
and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER
):
current_python_version = ".".join(str(x) for x in sys.version_info[:3])
required_python_version = ".".join(str(x) for x in REQUIRED_NEXT_PYTHON_VER[:2])
_LOGGER.warning(
(
"Support for the running Python version %s is deprecated and "
"will be removed in Home Assistant %s; "
"Please upgrade Python to %s"
),
current_python_version,
REQUIRED_NEXT_PYTHON_HA_RELEASE,
required_python_version,
if REQUIRED_NEXT_PYTHON_DATE and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER:
msg = (
"Support for the running Python version "
f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will "
f"be removed in the first release after {REQUIRED_NEXT_PYTHON_DATE}. "
"Please upgrade Python to "
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or "
"higher."
)
issue_registry.async_create_issue(
hass,
core.DOMAIN,
"python_version",
is_fixable=False,
severity=issue_registry.IssueSeverity.WARNING,
breaks_in_ha_version=REQUIRED_NEXT_PYTHON_HA_RELEASE,
translation_key="python_version",
translation_placeholders={
"current_python_version": current_python_version,
"required_python_version": required_python_version,
"breaks_in_ha_version": REQUIRED_NEXT_PYTHON_HA_RELEASE,
},
_LOGGER.warning(msg)
hass.components.persistent_notification.async_create(
msg, "Python version", "python_version"
)
return hass
@@ -339,9 +270,7 @@ def async_enable_logging(
This method must be run in the event loop.
"""
fmt = (
"%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
)
fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
datefmt = "%Y-%m-%d %H:%M:%S"
if not log_no_color:
@@ -381,7 +310,7 @@ def async_enable_logging(
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
sys.excepthook = lambda *args: logging.getLogger(None).exception(
"Uncaught exception", exc_info=args # type: ignore[arg-type]
"Uncaught exception", exc_info=args # type: ignore
)
threading.excepthook = lambda args: logging.getLogger(None).exception(
"Uncaught thread exception",
@@ -439,7 +368,8 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
This function is a coroutine.
"""
deps_dir = os.path.join(config_dir, "deps")
if (lib_dir := await async_get_user_site(deps_dir)) not in sys.path:
lib_dir = await async_get_user_site(deps_dir)
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir
@@ -448,14 +378,14 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
"""Get domains of components to set up."""
# Filter out the repeating and common config section [homeassistant]
domains = {key.partition(" ")[0] for key in config if key != core.DOMAIN}
domains = {key.split(" ")[0] for key in config if key != core.DOMAIN}
# Add config entry domains
if not hass.config.safe_mode:
domains.update(hass.config_entries.async_domains())
# Make sure the Hass.io component is loaded
if "SUPERVISOR" in os.environ:
if "HASSIO" in os.environ:
domains.add("hassio")
return domains
@@ -475,7 +405,7 @@ async def _async_watch_pending_setups(hass: core.HomeAssistant) -> None:
_LOGGER.debug("Integration remaining: %s", remaining_with_setup_started)
if remaining_with_setup_started or not previous_was_empty:
async_dispatcher_send(
hass, SIGNAL_BOOTSTRAP_INTEGRATIONS, remaining_with_setup_started
hass, SIGNAL_BOOTSTRAP_INTEGRATONS, remaining_with_setup_started
)
previous_was_empty = not remaining_with_setup_started
await asyncio.sleep(SLOW_STARTUP_CHECK_INTERVAL)
@@ -517,7 +447,7 @@ async def _async_set_up_integrations(
) -> None:
"""Set up all the integrations."""
hass.data[DATA_SETUP_STARTED] = {}
setup_time: dict[str, timedelta] = hass.data.setdefault(DATA_SETUP_TIME, {})
setup_time = hass.data[DATA_SETUP_TIME] = {}
watch_task = asyncio.create_task(_async_watch_pending_setups(hass))
@@ -526,16 +456,21 @@ async def _async_set_up_integrations(
# Resolve all dependencies so we know all integrations
# that will have to be loaded and start rightaway
integration_cache: dict[str, loader.Integration] = {}
to_resolve: set[str] = domains_to_setup
to_resolve = domains_to_setup
while to_resolve:
old_to_resolve: set[str] = to_resolve
old_to_resolve = to_resolve
to_resolve = set()
integrations_to_process = [
int_or_exc
for int_or_exc in (
await loader.async_get_integrations(hass, old_to_resolve)
).values()
for int_or_exc in await gather_with_concurrency(
loader.MAX_LOAD_CONCURRENTLY,
*(
loader.async_get_integration(hass, domain)
for domain in old_to_resolve
),
return_exceptions=True,
)
if isinstance(int_or_exc, loader.Integration)
]
resolve_dependencies_tasks = [
@@ -559,37 +494,26 @@ async def _async_set_up_integrations(
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
# Initialize recorder
if "recorder" in domains_to_setup:
recorder.async_initialize_recorder(hass)
logging_domains = domains_to_setup & LOGGING_INTEGRATIONS
# Load logging as soon as possible
if logging_domains := domains_to_setup & LOGGING_INTEGRATIONS:
if logging_domains:
_LOGGER.info("Setting up logging: %s", logging_domains)
await async_setup_multi_components(hass, logging_domains, config)
# Setup frontend
if frontend_domains := domains_to_setup & FRONTEND_INTEGRATIONS:
_LOGGER.info("Setting up frontend: %s", frontend_domains)
await async_setup_multi_components(hass, frontend_domains, config)
# Setup recorder
if recorder_domains := domains_to_setup & RECORDER_INTEGRATIONS:
_LOGGER.info("Setting up recorder: %s", recorder_domains)
await async_setup_multi_components(hass, recorder_domains, config)
# Start up debuggers. Start these first in case they want to wait.
if debuggers := domains_to_setup & DEBUGGER_INTEGRATIONS:
debuggers = domains_to_setup & DEBUGGER_INTEGRATIONS
if debuggers:
_LOGGER.debug("Setting up debuggers: %s", debuggers)
await async_setup_multi_components(hass, debuggers, config)
# calculate what components to setup in what stage
stage_1_domains: set[str] = set()
stage_1_domains = set()
# Find all dependencies of any dependency of any stage 1 integration that
# we plan on loading and promote them to stage 1. This is done only to not
# get misleading log messages
deps_promotion: set[str] = STAGE_1_INTEGRATIONS
# we plan on loading and promote them to stage 1
deps_promotion = STAGE_1_INTEGRATIONS
while deps_promotion:
old_deps_promotion = deps_promotion
deps_promotion = set()
@@ -600,18 +524,20 @@ async def _async_set_up_integrations(
stage_1_domains.add(domain)
if (dep_itg := integration_cache.get(domain)) is None:
dep_itg = integration_cache.get(domain)
if dep_itg is None:
continue
deps_promotion.update(dep_itg.all_dependencies)
stage_2_domains = (
domains_to_setup
- logging_domains
- frontend_domains
- recorder_domains
- debuggers
- stage_1_domains
stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains
# Load the registries
await asyncio.gather(
device_registry.async_load(hass),
entity_registry.async_load(hass),
area_registry.async_load(hass),
)
# Start setup
@@ -638,6 +564,19 @@ async def _async_set_up_integrations(
except asyncio.TimeoutError:
_LOGGER.warning("Setup timed out for stage 2 - moving forward")
watch_task.cancel()
async_dispatcher_send(hass, SIGNAL_BOOTSTRAP_INTEGRATONS, {})
_LOGGER.debug(
"Integration setup times: %s",
{
integration: timedelta.total_seconds()
for integration, timedelta in sorted(
setup_time.items(), key=lambda item: item[1].total_seconds() # type: ignore
)
},
)
# Wrap up startup
_LOGGER.debug("Waiting for startup to wrap up")
try:
@@ -645,16 +584,3 @@ async def _async_set_up_integrations(
await hass.async_block_till_done()
except asyncio.TimeoutError:
_LOGGER.warning("Setup timed out for bootstrap - moving forward")
watch_task.cancel()
async_dispatcher_send(hass, SIGNAL_BOOTSTRAP_INTEGRATIONS, {})
_LOGGER.debug(
"Integration setup times: %s",
{
integration: timedelta.total_seconds()
for integration, timedelta in sorted(
setup_time.items(), key=lambda item: item[1].total_seconds()
)
},
)
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "airthings",
"name": "Airthings",
"integrations": ["airthings", "airthings_ble"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "airvisual",
"name": "AirVisual",
"integrations": ["airvisual", "airvisual_pro"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "amazon",
"name": "Amazon",
"integrations": ["alexa", "amazon_polly", "aws", "route53"]
}
-12
View File
@@ -1,12 +0,0 @@
{
"domain": "apple",
"name": "Apple",
"integrations": [
"apple_tv",
"homekit_controller",
"homekit",
"ibeacon",
"icloud",
"itunes"
]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "aruba",
"name": "Aruba",
"integrations": ["aruba", "cppm_tracker"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "asterisk",
"name": "Asterisk",
"integrations": ["asterisk_cdr", "asterisk_mbox"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "august",
"name": "August Home",
"integrations": ["august", "yalexs_ble"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "cisco",
"name": "Cisco",
"integrations": ["cisco_ios", "cisco_mobility_express", "cisco_webex_teams"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "clicksend",
"name": "ClickSend",
"integrations": ["clicksend", "clicksend_tts"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "denon",
"name": "Denon",
"integrations": ["denon", "denonavr", "heos"]
}
-6
View File
@@ -1,6 +0,0 @@
{
"domain": "devolo",
"name": "devolo",
"integrations": ["devolo_home_control", "devolo_home_network"],
"iot_standards": ["zwave"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "dlna",
"name": "DLNA",
"integrations": ["dlna_dmr", "dlna_dms"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "elgato",
"name": "Elgato",
"integrations": ["avea", "elgato"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "emoncms",
"name": "emoncms",
"integrations": ["emoncms", "emoncms_history"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "epson",
"name": "Epson",
"integrations": ["epson", "epsonworkforce"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "eq3",
"name": "eQ-3",
"integrations": ["eq3btsmart", "maxcube"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "ffmpeg",
"name": "FFmpeg",
"integrations": ["ffmpeg", "ffmpeg_motion", "ffmpeg_noise"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "fritzbox",
"name": "FRITZ!Box",
"integrations": ["fritz", "fritzbox", "fritzbox_callmonitor"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "geonet",
"name": "GeoNet",
"integrations": ["geonetnz_quakes", "geonetnz_volcano"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "globalcache",
"name": "Global Caché",
"integrations": ["gc100", "itach"]
}
-20
View File
@@ -1,20 +0,0 @@
{
"domain": "google",
"name": "Google",
"integrations": [
"google_assistant",
"google_assistant_sdk",
"google_cloud",
"google_domains",
"google_maps",
"google_pubsub",
"google_sheets",
"google_translate",
"google_travel_time",
"google_wifi",
"google",
"nest",
"cast",
"dialogflow"
]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "hikvision",
"name": "Hikvision",
"integrations": ["hikvision", "hikvisioncam"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "homematic",
"name": "Homematic",
"integrations": ["homematic", "homematicip_cloud"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "honeywell",
"name": "Honeywell",
"integrations": ["lyric", "evohome", "honeywell"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "ibm",
"name": "IBM",
"integrations": ["watson_iot", "watson_tts"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "ikea",
"name": "IKEA",
"integrations": ["symfonisk", "tradfri"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "inovelli",
"name": "Inovelli",
"iot_standards": ["zigbee", "zwave"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "jasco",
"name": "Jasco",
"iot_standards": ["zwave"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "leviton",
"name": "Leviton",
"iot_standards": ["zwave"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "lg",
"name": "LG",
"integrations": ["lg_netcast", "lg_soundbar", "webostv"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "logitech",
"name": "Logitech",
"integrations": ["harmony", "ue_smart_radio", "squeezebox"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "lutron",
"name": "Lutron",
"integrations": ["lutron", "lutron_caseta", "homeworks"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "melnor",
"name": "Melnor",
"integrations": ["melnor", "raincloud"]
}
-16
View File
@@ -1,16 +0,0 @@
{
"domain": "microsoft",
"name": "Microsoft",
"integrations": [
"azure_devops",
"azure_event_hub",
"azure_service_bus",
"microsoft_face_detect",
"microsoft_face_identify",
"microsoft_face",
"microsoft",
"msteams",
"xbox",
"xbox_live"
]
}
-12
View File
@@ -1,12 +0,0 @@
{
"domain": "mqtt",
"name": "MQTT",
"integrations": [
"manual_mqtt",
"mqtt",
"mqtt_eventstream",
"mqtt_json",
"mqtt_room",
"mqtt_statestream"
]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "netgear",
"name": "NETGEAR",
"integrations": ["netgear", "netgear_lte"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "openwrt",
"name": "OpenWrt",
"integrations": ["luci", "ubus"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "panasonic",
"name": "Panasonic",
"integrations": ["panasonic_bluray", "panasonic_viera"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "philips",
"name": "Philips",
"integrations": ["dynalite", "hue", "philips_js"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "qnap",
"name": "QNAP",
"integrations": ["qnap", "qnap_qsw"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "raspberry_pi",
"name": "Raspberry Pi",
"integrations": ["raspberry_pi", "rpi_camera", "rpi_power", "remote_rpi_gpio"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "russound",
"name": "Russound",
"integrations": ["russound_rio", "russound_rnet"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "samsung",
"name": "Samsung",
"integrations": ["familyhub", "samsungtv", "syncthru"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "solaredge",
"name": "SolarEdge",
"integrations": ["solaredge", "solaredge_local"]
}

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