mirror of
https://github.com/home-assistant/core.git
synced 2026-05-06 08:36:42 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57263bb65a | |||
| 86533e3599 |
@@ -1,144 +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/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
|
||||
+250
-597
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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/).
|
||||
|
||||
[](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/).
|
||||
|
||||
[](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,24 +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
|
||||
on the device page (and on the integration dashboard).
|
||||
|
||||
**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:
|
||||
@@ -104,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+86
-131
@@ -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.1.0
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.3.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.1.0
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.3.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.1.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.3.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.09.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.1.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.09.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.1.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.1.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
-735
File diff suppressed because it is too large
Load Diff
@@ -7,15 +7,14 @@ on:
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3
|
||||
- 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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@v6.0.1
|
||||
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@v6.0.1
|
||||
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@v6.0.1
|
||||
uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
only-labels: "needs-more-information"
|
||||
|
||||
@@ -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.1.0
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.3.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.1.0
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v2.2.2
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
|
||||
@@ -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.1.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.1.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.1.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"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/config
|
||||
config/*
|
||||
config2/*
|
||||
|
||||
tests/testing_config/deps
|
||||
|
||||
@@ -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
|
||||
+23
-43
@@ -1,42 +1,42 @@
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.1.0
|
||||
rev: v2.23.3
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.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.1.0
|
||||
rev: v2.0.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa,pullrequests
|
||||
- --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: 4.0.1
|
||||
exclude: ^tests/fixtures/
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.9.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- pycodestyle==2.8.0
|
||||
- pyflakes==2.4.0
|
||||
- pycodestyle==2.7.0
|
||||
- pyflakes==2.3.1
|
||||
- flake8-docstrings==1.6.0
|
||||
- pydocstyle==6.1.1
|
||||
- flake8-comprehensions==3.10.0
|
||||
- flake8-noqa==1.2.8
|
||||
- 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:
|
||||
@@ -45,7 +45,7 @@ repos:
|
||||
- --configfile=tests/bandit.yaml
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.9.3
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
@@ -61,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.6.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$
|
||||
@@ -94,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
|
||||
@@ -107,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
@@ -1,6 +1,5 @@
|
||||
*.md
|
||||
.strict-typing
|
||||
azure-*.yml
|
||||
docs/source/_templates/*
|
||||
homeassistant/components/*/translations/*.json
|
||||
homeassistant/generated/*
|
||||
tests/fixtures/*
|
||||
|
||||
+5
-9
@@ -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
-195
@@ -2,300 +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.exceptions
|
||||
homeassistant.core
|
||||
homeassistant.loader
|
||||
homeassistant.requirements
|
||||
homeassistant.runner
|
||||
homeassistant.setup
|
||||
homeassistant.auth.auth_store
|
||||
homeassistant.auth.providers.*
|
||||
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.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.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.cover.*
|
||||
homeassistant.components.clickatell.*
|
||||
homeassistant.components.clicksend.*
|
||||
homeassistant.components.cpuspeed.*
|
||||
homeassistant.components.crownstone.*
|
||||
homeassistant.components.deconz.*
|
||||
homeassistant.components.demo.*
|
||||
homeassistant.components.device_automation.*
|
||||
homeassistant.components.device_tracker.*
|
||||
homeassistant.components.devolo_home_control.*
|
||||
homeassistant.components.devolo_home_network.*
|
||||
homeassistant.components.dhcp.*
|
||||
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.history.*
|
||||
homeassistant.components.homeassistant.triggers.event
|
||||
homeassistant.components.homeassistant_alerts.*
|
||||
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.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.lookin.*
|
||||
homeassistant.components.luftdaten.*
|
||||
homeassistant.components.mailbox.*
|
||||
homeassistant.components.media_player.*
|
||||
homeassistant.components.media_source.*
|
||||
homeassistant.components.metoffice.*
|
||||
homeassistant.components.mikrotik.*
|
||||
homeassistant.components.mjpeg.*
|
||||
homeassistant.components.modbus.*
|
||||
homeassistant.components.modem_callerid.*
|
||||
homeassistant.components.moon.*
|
||||
homeassistant.components.mysensors.*
|
||||
homeassistant.components.nam.*
|
||||
homeassistant.components.nanoleaf.*
|
||||
homeassistant.components.neato.*
|
||||
homeassistant.components.nest.*
|
||||
homeassistant.components.netatmo.*
|
||||
homeassistant.components.network.*
|
||||
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.pvoutput.*
|
||||
homeassistant.components.qnap_qsw.*
|
||||
homeassistant.components.rainmachine.*
|
||||
homeassistant.components.rdw.*
|
||||
homeassistant.components.radarr.*
|
||||
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.samsungtv.*
|
||||
homeassistant.components.scene.*
|
||||
homeassistant.components.schedule.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.senseme.*
|
||||
homeassistant.components.sensibo.*
|
||||
homeassistant.components.sensor.*
|
||||
homeassistant.components.senz.*
|
||||
homeassistant.components.shelly.*
|
||||
homeassistant.components.simplisafe.*
|
||||
homeassistant.components.skybell.*
|
||||
homeassistant.components.slack.*
|
||||
homeassistant.components.sleepiq.*
|
||||
homeassistant.components.smhi.*
|
||||
homeassistant.components.snooz.*
|
||||
homeassistant.components.sonarr.*
|
||||
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.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.*
|
||||
|
||||
Vendored
-8
@@ -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/
|
||||
|
||||
Vendored
+9
-20
@@ -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
-1329
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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.
|
||||
|
||||
+19
-16
@@ -7,27 +7,30 @@ 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}" \
|
||||
-r homeassistant/requirements.txt --use-deprecated=legacy-resolver
|
||||
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 \
|
||||
&& pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
||||
-r homeassistant/requirements_all.txt --use-deprecated=legacy-resolver
|
||||
|
||||
## Setup Home Assistant Core
|
||||
## Setup Home Assistant
|
||||
COPY . homeassistant/
|
||||
RUN \
|
||||
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
||||
-e ./homeassistant --use-deprecated=legacy-resolver \
|
||||
-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
@@ -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,3 +1,4 @@
|
||||
include README.rst
|
||||
include LICENSE.md
|
||||
graft homeassistant
|
||||
recursive-exclude * *.py[co]
|
||||
|
||||
+3
-3
@@ -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
@@ -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
@@ -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.10.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.10.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.10.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.10.0
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.10.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
@@ -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 |
+157
-38
@@ -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(
|
||||
@@ -97,6 +88,12 @@ def get_arguments() -> argparse.Namespace:
|
||||
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,
|
||||
@@ -113,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()
|
||||
@@ -151,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,
|
||||
@@ -185,16 +311,9 @@ def main() -> int:
|
||||
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
|
||||
|
||||
|
||||
@@ -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):
|
||||
@@ -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)
|
||||
@@ -357,7 +328,7 @@ class AuthManager:
|
||||
|
||||
if provider is not None and hasattr(provider, "async_will_remove_credentials"):
|
||||
# https://github.com/python/mypy/issues/1424
|
||||
await provider.async_will_remove_credentials(credentials) # type: ignore[attr-defined]
|
||||
await provider.async_will_remove_credentials(credentials) # type: ignore
|
||||
|
||||
await self._store.async_remove_credentials(credentials)
|
||||
|
||||
@@ -370,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)
|
||||
@@ -384,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)
|
||||
@@ -475,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
|
||||
@@ -515,7 +466,7 @@ class AuthManager:
|
||||
},
|
||||
refresh_token.jwt_key,
|
||||
algorithm="HS256",
|
||||
)
|
||||
).decode()
|
||||
|
||||
@callback
|
||||
def _async_resolve_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
|
||||
|
||||
|
||||
@@ -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"]:
|
||||
@@ -455,14 +446,16 @@ class AuthStore:
|
||||
)
|
||||
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
|
||||
@@ -483,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
|
||||
@@ -514,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()
|
||||
]
|
||||
|
||||
@@ -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:
|
||||
@@ -129,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",
|
||||
@@ -142,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:
|
||||
@@ -168,7 +168,7 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.Modul
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -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.7.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
|
||||
@@ -335,6 +338,7 @@ class NotifySetupFlow(SetupFlow):
|
||||
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
|
||||
)
|
||||
|
||||
@@ -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.7.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)
|
||||
|
||||
@@ -77,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."""
|
||||
@@ -93,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."""
|
||||
@@ -108,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:
|
||||
@@ -137,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:
|
||||
@@ -145,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."""
|
||||
@@ -162,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)
|
||||
@@ -182,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
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,7 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Callable
|
||||
from typing import Callable
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
auth_module, "async_initialize_login_mfa_step"
|
||||
):
|
||||
try:
|
||||
await auth_module.async_initialize_login_mfa_step( # type: ignore[attr-defined]
|
||||
await auth_module.async_initialize_login_mfa_step( # type: ignore
|
||||
self.user.id
|
||||
)
|
||||
except HomeAssistantError:
|
||||
@@ -272,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,
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,7 +93,9 @@ 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(
|
||||
@@ -121,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.
|
||||
@@ -149,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))
|
||||
|
||||
@@ -205,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")
|
||||
@@ -333,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 +0,0 @@
|
||||
"""Backports from newer Python versions."""
|
||||
@@ -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")
|
||||
@@ -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
|
||||
|
||||
+75
-124
@@ -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, persistent_notification
|
||||
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
|
||||
@@ -56,6 +46,7 @@ DATA_LOGGING = "logging"
|
||||
|
||||
LOG_SLOW_STARTUP_INTERVAL = 60
|
||||
SLOW_STARTUP_CHECK_INTERVAL = 1
|
||||
SIGNAL_BOOTSTRAP_INTEGRATONS = "bootstrap_integrations"
|
||||
|
||||
STAGE_1_TIMEOUT = 120
|
||||
STAGE_2_TIMEOUT = 300
|
||||
@@ -65,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",
|
||||
}
|
||||
|
||||
|
||||
@@ -130,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:
|
||||
@@ -169,11 +149,8 @@ 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.internal_url = old_config.internal_url
|
||||
hass.config.external_url = old_config.external_url
|
||||
@@ -264,20 +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
|
||||
):
|
||||
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 Home Assistant {REQUIRED_NEXT_PYTHON_HA_RELEASE}. "
|
||||
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[:2])}."
|
||||
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or "
|
||||
"higher."
|
||||
)
|
||||
_LOGGER.warning(msg)
|
||||
persistent_notification.async_create(
|
||||
hass, msg, "Python version", "python_version"
|
||||
hass.components.persistent_notification.async_create(
|
||||
msg, "Python version", "python_version"
|
||||
)
|
||||
|
||||
return hass
|
||||
@@ -295,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:
|
||||
@@ -337,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",
|
||||
@@ -395,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
|
||||
|
||||
@@ -411,7 +385,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
|
||||
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
|
||||
@@ -431,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)
|
||||
@@ -473,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))
|
||||
|
||||
@@ -482,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 = [
|
||||
@@ -515,56 +494,26 @@ async def _async_set_up_integrations(
|
||||
|
||||
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
||||
|
||||
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),
|
||||
)
|
||||
|
||||
# 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()
|
||||
@@ -575,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
|
||||
@@ -613,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:
|
||||
@@ -620,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()
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "airthings",
|
||||
"name": "Airthings",
|
||||
"integrations": ["airthings", "airthings_ble"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "amazon",
|
||||
"name": "Amazon",
|
||||
"integrations": ["alexa", "amazon_polly", "aws", "route53"]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"domain": "apple",
|
||||
"name": "Apple",
|
||||
"integrations": [
|
||||
"apple_tv",
|
||||
"homekit_controller",
|
||||
"homekit",
|
||||
"ibeacon",
|
||||
"icloud",
|
||||
"itunes"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "aruba",
|
||||
"name": "Aruba",
|
||||
"integrations": ["aruba", "cppm_tracker"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "asterisk",
|
||||
"name": "Asterisk",
|
||||
"integrations": ["asterisk_cdr", "asterisk_mbox"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "august",
|
||||
"name": "August Home",
|
||||
"integrations": ["august", "yalexs_ble"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "cisco",
|
||||
"name": "Cisco",
|
||||
"integrations": ["cisco_ios", "cisco_mobility_express", "cisco_webex_teams"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "clicksend",
|
||||
"name": "ClickSend",
|
||||
"integrations": ["clicksend", "clicksend_tts"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "denon",
|
||||
"name": "Denon",
|
||||
"integrations": ["denon", "denonavr", "heos"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"domain": "devolo",
|
||||
"name": "devolo",
|
||||
"integrations": ["devolo_home_control", "devolo_home_network"],
|
||||
"iot_standards": ["zwave"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "dlna",
|
||||
"name": "DLNA",
|
||||
"integrations": ["dlna_dmr", "dlna_dms"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "elgato",
|
||||
"name": "Elgato",
|
||||
"integrations": ["avea", "elgato"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "emoncms",
|
||||
"name": "emoncms",
|
||||
"integrations": ["emoncms", "emoncms_history"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "epson",
|
||||
"name": "Epson",
|
||||
"integrations": ["epson", "epsonworkforce"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "eq3",
|
||||
"name": "eQ-3",
|
||||
"integrations": ["eq3btsmart", "maxcube"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "ffmpeg",
|
||||
"name": "FFmpeg",
|
||||
"integrations": ["ffmpeg", "ffmpeg_motion", "ffmpeg_noise"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "fritzbox",
|
||||
"name": "FRITZ!Box",
|
||||
"integrations": ["fritz", "fritzbox", "fritzbox_callmonitor"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "geonet",
|
||||
"name": "GeoNet",
|
||||
"integrations": ["geonetnz_quakes", "geonetnz_volcano"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "globalcache",
|
||||
"name": "Global Caché",
|
||||
"integrations": ["gc100", "itach"]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"domain": "google",
|
||||
"name": "Google",
|
||||
"integrations": [
|
||||
"google_assistant",
|
||||
"google_cloud",
|
||||
"google_domains",
|
||||
"google_maps",
|
||||
"google_pubsub",
|
||||
"google_sheets",
|
||||
"google_translate",
|
||||
"google_travel_time",
|
||||
"google_wifi",
|
||||
"google",
|
||||
"nest",
|
||||
"cast",
|
||||
"hangouts",
|
||||
"dialogflow"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "hikvision",
|
||||
"name": "Hikvision",
|
||||
"integrations": ["hikvision", "hikvisioncam"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "homematic",
|
||||
"name": "Homematic",
|
||||
"integrations": ["homematic", "homematicip_cloud"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "honeywell",
|
||||
"name": "Honeywell",
|
||||
"integrations": ["lyric", "evohome", "honeywell"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "ibm",
|
||||
"name": "IBM",
|
||||
"integrations": ["watson_iot", "watson_tts"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "ikea",
|
||||
"name": "IKEA",
|
||||
"integrations": ["symfonisk", "tradfri"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "inovelli",
|
||||
"name": "Inovelli",
|
||||
"iot_standards": ["zigbee", "zwave"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "jasco",
|
||||
"name": "Jasco",
|
||||
"iot_standards": ["zwave"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "leviton",
|
||||
"name": "Leviton",
|
||||
"iot_standards": ["zwave"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "lg",
|
||||
"name": "LG",
|
||||
"integrations": ["lg_netcast", "lg_soundbar", "webostv"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "logitech",
|
||||
"name": "Logitech",
|
||||
"integrations": ["harmony", "ue_smart_radio", "squeezebox"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "lutron",
|
||||
"name": "Lutron",
|
||||
"integrations": ["lutron", "lutron_caseta", "homeworks"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "melnor",
|
||||
"name": "Melnor",
|
||||
"integrations": ["melnor", "raincloud"]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"domain": "mqtt",
|
||||
"name": "MQTT",
|
||||
"integrations": [
|
||||
"manual_mqtt",
|
||||
"mqtt",
|
||||
"mqtt_eventstream",
|
||||
"mqtt_json",
|
||||
"mqtt_room",
|
||||
"mqtt_statestream"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "netgear",
|
||||
"name": "NETGEAR",
|
||||
"integrations": ["netgear", "netgear_lte"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "openwrt",
|
||||
"name": "OpenWrt",
|
||||
"integrations": ["luci", "ubus"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "panasonic",
|
||||
"name": "Panasonic",
|
||||
"integrations": ["panasonic_bluray", "panasonic_viera"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "philips",
|
||||
"name": "Philips",
|
||||
"integrations": ["dynalite", "hue", "philips_js"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "qnap",
|
||||
"name": "QNAP",
|
||||
"integrations": ["qnap", "qnap_qsw"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "raspberry_pi",
|
||||
"name": "Raspberry Pi",
|
||||
"integrations": ["raspberry_pi", "rpi_camera", "rpi_power", "remote_rpi_gpio"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "russound",
|
||||
"name": "Russound",
|
||||
"integrations": ["russound_rio", "russound_rnet"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "samsung",
|
||||
"name": "Samsung",
|
||||
"integrations": ["familyhub", "samsungtv", "syncthru"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "solaredge",
|
||||
"name": "SolarEdge",
|
||||
"integrations": ["solaredge", "solaredge_local"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "sony",
|
||||
"name": "Sony",
|
||||
"integrations": ["braviatv", "ps4", "sony_projector", "songpal"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "synology",
|
||||
"name": "Synology",
|
||||
"integrations": ["synology_chat", "synology_dsm", "synology_srm"]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "telegram",
|
||||
"name": "Telegram",
|
||||
"integrations": ["telegram", "telegram_bot"]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user