Merge pull request #26710 from home-assistant/rc

0.99.0
This commit is contained in:
Paulus Schoutsen 2019-09-18 15:29:01 -07:00 committed by GitHub
commit 884591a105
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1177 changed files with 24522 additions and 6471 deletions

View File

@ -31,7 +31,6 @@ omit =
homeassistant/components/amcrest/* homeassistant/components/amcrest/*
homeassistant/components/ampio/* homeassistant/components/ampio/*
homeassistant/components/android_ip_webcam/* homeassistant/components/android_ip_webcam/*
homeassistant/components/androidtv/*
homeassistant/components/anel_pwrctrl/switch.py homeassistant/components/anel_pwrctrl/switch.py
homeassistant/components/anthemav/media_player.py homeassistant/components/anthemav/media_player.py
homeassistant/components/apache_kafka/* homeassistant/components/apache_kafka/*
@ -51,6 +50,7 @@ omit =
homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_cdr/mailbox.py
homeassistant/components/asterisk_mbox/* homeassistant/components/asterisk_mbox/*
homeassistant/components/asuswrt/device_tracker.py homeassistant/components/asuswrt/device_tracker.py
homeassistant/components/atome/*
homeassistant/components/august/* homeassistant/components/august/*
homeassistant/components/aurora_abb_powerone/sensor.py homeassistant/components/aurora_abb_powerone/sensor.py
homeassistant/components/automatic/device_tracker.py homeassistant/components/automatic/device_tracker.py
@ -58,6 +58,7 @@ omit =
homeassistant/components/avion/light.py homeassistant/components/avion/light.py
homeassistant/components/azure_event_hub/* homeassistant/components/azure_event_hub/*
homeassistant/components/baidu/tts.py homeassistant/components/baidu/tts.py
homeassistant/components/beewi_smartclim/sensor.py
homeassistant/components/bbb_gpio/* homeassistant/components/bbb_gpio/*
homeassistant/components/bbox/device_tracker.py homeassistant/components/bbox/device_tracker.py
homeassistant/components/bbox/sensor.py homeassistant/components/bbox/sensor.py
@ -93,6 +94,7 @@ omit =
homeassistant/components/canary/camera.py homeassistant/components/canary/camera.py
homeassistant/components/cast/* homeassistant/components/cast/*
homeassistant/components/cert_expiry/sensor.py homeassistant/components/cert_expiry/sensor.py
homeassistant/components/cert_expiry/helper.py
homeassistant/components/channels/media_player.py homeassistant/components/channels/media_player.py
homeassistant/components/cisco_ios/device_tracker.py homeassistant/components/cisco_ios/device_tracker.py
homeassistant/components/cisco_mobility_express/device_tracker.py homeassistant/components/cisco_mobility_express/device_tracker.py
@ -247,6 +249,7 @@ omit =
homeassistant/components/greeneye_monitor/sensor.py homeassistant/components/greeneye_monitor/sensor.py
homeassistant/components/greenwave/light.py homeassistant/components/greenwave/light.py
homeassistant/components/group/notify.py homeassistant/components/group/notify.py
homeassistant/components/growatt_server/sensor.py
homeassistant/components/gstreamer/media_player.py homeassistant/components/gstreamer/media_player.py
homeassistant/components/gtfs/sensor.py homeassistant/components/gtfs/sensor.py
homeassistant/components/gtt/sensor.py homeassistant/components/gtt/sensor.py
@ -285,6 +288,10 @@ omit =
homeassistant/components/hydrawise/* homeassistant/components/hydrawise/*
homeassistant/components/hyperion/light.py homeassistant/components/hyperion/light.py
homeassistant/components/ialarm/alarm_control_panel.py homeassistant/components/ialarm/alarm_control_panel.py
homeassistant/components/iaqualink/climate.py
homeassistant/components/iaqualink/light.py
homeassistant/components/iaqualink/sensor.py
homeassistant/components/iaqualink/switch.py
homeassistant/components/icloud/device_tracker.py homeassistant/components/icloud/device_tracker.py
homeassistant/components/idteck_prox/* homeassistant/components/idteck_prox/*
homeassistant/components/ifttt/* homeassistant/components/ifttt/*
@ -338,6 +345,7 @@ omit =
homeassistant/components/limitlessled/light.py homeassistant/components/limitlessled/light.py
homeassistant/components/linksys_ap/device_tracker.py homeassistant/components/linksys_ap/device_tracker.py
homeassistant/components/linksys_smart/device_tracker.py homeassistant/components/linksys_smart/device_tracker.py
homeassistant/components/linky/__init__.py
homeassistant/components/linky/sensor.py homeassistant/components/linky/sensor.py
homeassistant/components/linode/* homeassistant/components/linode/*
homeassistant/components/linux_battery/sensor.py homeassistant/components/linux_battery/sensor.py
@ -427,6 +435,7 @@ omit =
homeassistant/components/nut/sensor.py homeassistant/components/nut/sensor.py
homeassistant/components/nx584/alarm_control_panel.py homeassistant/components/nx584/alarm_control_panel.py
homeassistant/components/nzbget/sensor.py homeassistant/components/nzbget/sensor.py
homeassistant/components/obihai/*
homeassistant/components/octoprint/* homeassistant/components/octoprint/*
homeassistant/components/oem/climate.py homeassistant/components/oem/climate.py
homeassistant/components/oasa_telematics/sensor.py homeassistant/components/oasa_telematics/sensor.py
@ -467,8 +476,7 @@ omit =
homeassistant/components/pioneer/media_player.py homeassistant/components/pioneer/media_player.py
homeassistant/components/pjlink/media_player.py homeassistant/components/pjlink/media_player.py
homeassistant/components/plaato/* homeassistant/components/plaato/*
homeassistant/components/plex/media_player.py homeassistant/components/plex/*
homeassistant/components/plex/sensor.py
homeassistant/components/plugwise/* homeassistant/components/plugwise/*
homeassistant/components/plum_lightpad/* homeassistant/components/plum_lightpad/*
homeassistant/components/pocketcasts/sensor.py homeassistant/components/pocketcasts/sensor.py
@ -563,6 +571,7 @@ omit =
homeassistant/components/skybeacon/sensor.py homeassistant/components/skybeacon/sensor.py
homeassistant/components/skybell/* homeassistant/components/skybell/*
homeassistant/components/slack/notify.py homeassistant/components/slack/notify.py
homeassistant/components/slide/*
homeassistant/components/sma/sensor.py homeassistant/components/sma/sensor.py
homeassistant/components/smappee/* homeassistant/components/smappee/*
homeassistant/components/smarty/* homeassistant/components/smarty/*
@ -572,6 +581,7 @@ omit =
homeassistant/components/snmp/* homeassistant/components/snmp/*
homeassistant/components/sochain/sensor.py homeassistant/components/sochain/sensor.py
homeassistant/components/socialblade/sensor.py homeassistant/components/socialblade/sensor.py
homeassistant/components/solaredge/__init__.py
homeassistant/components/solaredge/sensor.py homeassistant/components/solaredge/sensor.py
homeassistant/components/solaredge_local/sensor.py homeassistant/components/solaredge_local/sensor.py
homeassistant/components/solax/sensor.py homeassistant/components/solax/sensor.py
@ -667,6 +677,7 @@ omit =
homeassistant/components/ue_smart_radio/media_player.py homeassistant/components/ue_smart_radio/media_player.py
homeassistant/components/upcloud/* homeassistant/components/upcloud/*
homeassistant/components/upnp/* homeassistant/components/upnp/*
homeassistant/components/upc_connect/*
homeassistant/components/ups/sensor.py homeassistant/components/ups/sensor.py
homeassistant/components/uptimerobot/binary_sensor.py homeassistant/components/uptimerobot/binary_sensor.py
homeassistant/components/uscis/sensor.py homeassistant/components/uscis/sensor.py
@ -689,6 +700,8 @@ omit =
homeassistant/components/vesync/const.py homeassistant/components/vesync/const.py
homeassistant/components/vesync/switch.py homeassistant/components/vesync/switch.py
homeassistant/components/viaggiatreno/sensor.py homeassistant/components/viaggiatreno/sensor.py
homeassistant/components/vicare/*
homeassistant/components/vivotek/camera.py
homeassistant/components/vizio/media_player.py homeassistant/components/vizio/media_player.py
homeassistant/components/vlc/media_player.py homeassistant/components/vlc/media_player.py
homeassistant/components/vlc_telnet/media_player.py homeassistant/components/vlc_telnet/media_player.py

View File

@ -3,16 +3,14 @@
"name": "Home Assistant Dev", "name": "Home Assistant Dev",
"context": "..", "context": "..",
"dockerFile": "../Dockerfile.dev", "dockerFile": "../Dockerfile.dev",
"postCreateCommand": "pip3 install -e .", "postCreateCommand": "mkdir -p config && pip3 install -e .",
"appPort": 8123, "appPort": 8123,
"runArgs": [ "runArgs": ["-e", "GIT_EDITOR=\"code --wait\""],
"-e",
"GIT_EDITOR=\"code --wait\""
],
"extensions": [ "extensions": [
"ms-python.python", "ms-python.python",
"ms-azure-devops.azure-pipelines", "ms-azure-devops.azure-pipelines",
"redhat.vscode-yaml" "redhat.vscode-yaml",
"esbenp.prettier-vscode"
], ],
"settings": { "settings": {
"python.pythonPath": "/usr/local/bin/python", "python.pythonPath": "/usr/local/bin/python",

1
.gitignore vendored
View File

@ -64,6 +64,7 @@ nosetests.xml
htmlcov/ htmlcov/
test-reports/ test-reports/
test-results.xml test-results.xml
test-output.xml
# Translations # Translations
*.mo *.mo

View File

@ -16,18 +16,14 @@ addons:
matrix: matrix:
fast_finish: true fast_finish: true
include: include:
- python: "3.6.0" - python: "3.6.1"
env: TOXENV=lint env: TOXENV=lint
dist: trusty - python: "3.6.1"
- python: "3.6.0"
env: TOXENV=pylint env: TOXENV=pylint
dist: trusty - python: "3.6.1"
- python: "3.6.0"
env: TOXENV=typing env: TOXENV=typing
dist: trusty - python: "3.6.1"
- python: "3.6.0"
env: TOXENV=py36 env: TOXENV=py36
dist: trusty
- python: "3.7" - python: "3.7"
env: TOXENV=py37 env: TOXENV=py37

27
.vscode/tasks.json vendored
View File

@ -7,7 +7,7 @@
"command": "hass -c ./config", "command": "hass -c ./config",
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true, "isDefault": true
}, },
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
@ -19,9 +19,10 @@
"label": "Pytest", "label": "Pytest",
"type": "shell", "type": "shell",
"command": "pytest --timeout=10 tests", "command": "pytest --timeout=10 tests",
"dependsOn": ["Install all Test Requirements"],
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true, "isDefault": true
}, },
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
@ -35,7 +36,7 @@
"command": "flake8 homeassistant tests", "command": "flake8 homeassistant tests",
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true, "isDefault": true
}, },
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
@ -47,12 +48,10 @@
"label": "Pylint", "label": "Pylint",
"type": "shell", "type": "shell",
"command": "pylint homeassistant", "command": "pylint homeassistant",
"dependsOn": [ "dependsOn": ["Install all Requirements"],
"Install all Requirements"
],
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true, "isDefault": true
}, },
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
@ -87,6 +86,20 @@
"panel": "new" "panel": "new"
}, },
"problemMatcher": [] "problemMatcher": []
},
{
"label": "Install all Test Requirements",
"type": "shell",
"command": "pip3 install -r requirements_test_all.txt -c homeassistant/package_constraints.txt",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
} }
] ]
} }

View File

@ -28,6 +28,7 @@ homeassistant/components/arcam_fmj/* @elupus
homeassistant/components/arduino/* @fabaff homeassistant/components/arduino/* @fabaff
homeassistant/components/arest/* @fabaff homeassistant/components/arest/* @fabaff
homeassistant/components/asuswrt/* @kennedyshead homeassistant/components/asuswrt/* @kennedyshead
homeassistant/components/atome/* @baqs
homeassistant/components/aurora_abb_powerone/* @davet2001 homeassistant/components/aurora_abb_powerone/* @davet2001
homeassistant/components/auth/* @home-assistant/core homeassistant/components/auth/* @home-assistant/core
homeassistant/components/automatic/* @armills homeassistant/components/automatic/* @armills
@ -37,6 +38,7 @@ homeassistant/components/awair/* @danielsjf
homeassistant/components/aws/* @awarecan @robbiet480 homeassistant/components/aws/* @awarecan @robbiet480
homeassistant/components/axis/* @kane610 homeassistant/components/axis/* @kane610
homeassistant/components/azure_event_hub/* @eavanvalkenburg homeassistant/components/azure_event_hub/* @eavanvalkenburg
homeassistant/components/beewi_smartclim/* @alemuro
homeassistant/components/bitcoin/* @fabaff homeassistant/components/bitcoin/* @fabaff
homeassistant/components/bizkaibus/* @UgaitzEtxebarria homeassistant/components/bizkaibus/* @UgaitzEtxebarria
homeassistant/components/blink/* @fronzbot homeassistant/components/blink/* @fronzbot
@ -46,6 +48,7 @@ homeassistant/components/broadlink/* @danielhiversen
homeassistant/components/brunt/* @eavanvalkenburg homeassistant/components/brunt/* @eavanvalkenburg
homeassistant/components/bt_smarthub/* @jxwolstenholme homeassistant/components/bt_smarthub/* @jxwolstenholme
homeassistant/components/buienradar/* @mjj4791 @ties homeassistant/components/buienradar/* @mjj4791 @ties
homeassistant/components/cert_expiry/* @cereal2nd
homeassistant/components/cisco_ios/* @fbradyirl homeassistant/components/cisco_ios/* @fbradyirl
homeassistant/components/cisco_mobility_express/* @fbradyirl homeassistant/components/cisco_mobility_express/* @fbradyirl
homeassistant/components/cisco_webex_teams/* @fbradyirl homeassistant/components/cisco_webex_teams/* @fbradyirl
@ -107,6 +110,7 @@ homeassistant/components/google_translate/* @awarecan
homeassistant/components/google_travel_time/* @robbiet480 homeassistant/components/google_travel_time/* @robbiet480
homeassistant/components/gpsd/* @fabaff homeassistant/components/gpsd/* @fabaff
homeassistant/components/group/* @home-assistant/core homeassistant/components/group/* @home-assistant/core
homeassistant/components/growatt_server/* @indykoning
homeassistant/components/gtfs/* @robbiet480 homeassistant/components/gtfs/* @robbiet480
homeassistant/components/harmony/* @ehendrix23 homeassistant/components/harmony/* @ehendrix23
homeassistant/components/hassio/* @home-assistant/hass-io homeassistant/components/hassio/* @home-assistant/hass-io
@ -119,12 +123,14 @@ homeassistant/components/hive/* @Rendili @KJonline
homeassistant/components/homeassistant/* @home-assistant/core homeassistant/components/homeassistant/* @home-assistant/core
homeassistant/components/homekit_controller/* @Jc2k homeassistant/components/homekit_controller/* @Jc2k
homeassistant/components/homematic/* @pvizeli @danielperna84 homeassistant/components/homematic/* @pvizeli @danielperna84
homeassistant/components/homematicip_cloud/* @SukramJ
homeassistant/components/honeywell/* @zxdavb homeassistant/components/honeywell/* @zxdavb
homeassistant/components/html5/* @robbiet480 homeassistant/components/html5/* @robbiet480
homeassistant/components/http/* @home-assistant/core homeassistant/components/http/* @home-assistant/core
homeassistant/components/huawei_lte/* @scop homeassistant/components/huawei_lte/* @scop
homeassistant/components/huawei_router/* @abmantis homeassistant/components/huawei_router/* @abmantis
homeassistant/components/hue/* @balloob homeassistant/components/hue/* @balloob
homeassistant/components/iaqualink/* @flz
homeassistant/components/ign_sismologia/* @exxamalte homeassistant/components/ign_sismologia/* @exxamalte
homeassistant/components/incomfort/* @zxdavb homeassistant/components/incomfort/* @zxdavb
homeassistant/components/influxdb/* @fabaff homeassistant/components/influxdb/* @fabaff
@ -150,7 +156,7 @@ homeassistant/components/life360/* @pnbruckner
homeassistant/components/lifx/* @amelchio homeassistant/components/lifx/* @amelchio
homeassistant/components/lifx_cloud/* @amelchio homeassistant/components/lifx_cloud/* @amelchio
homeassistant/components/lifx_legacy/* @amelchio homeassistant/components/lifx_legacy/* @amelchio
homeassistant/components/linky/* @tiste @Quentame homeassistant/components/linky/* @Quentame
homeassistant/components/linux_battery/* @fabaff homeassistant/components/linux_battery/* @fabaff
homeassistant/components/liveboxplaytv/* @pschmitt homeassistant/components/liveboxplaytv/* @pschmitt
homeassistant/components/logger/* @home-assistant/core homeassistant/components/logger/* @home-assistant/core
@ -188,7 +194,10 @@ homeassistant/components/no_ip/* @fabaff
homeassistant/components/notify/* @home-assistant/core homeassistant/components/notify/* @home-assistant/core
homeassistant/components/notion/* @bachya homeassistant/components/notion/* @bachya
homeassistant/components/nsw_fuel_station/* @nickw444 homeassistant/components/nsw_fuel_station/* @nickw444
homeassistant/components/nuki/* @pschmitt homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte
homeassistant/components/nuki/* @pvizeli
homeassistant/components/nws/* @MatthewFlamm
homeassistant/components/obihai/* @dshokouhi
homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/ohmconnect/* @robbiet480
homeassistant/components/onboarding/* @home-assistant/core homeassistant/components/onboarding/* @home-assistant/core
homeassistant/components/opentherm_gw/* @mvn23 homeassistant/components/opentherm_gw/* @mvn23
@ -203,6 +212,7 @@ homeassistant/components/philips_js/* @elupus
homeassistant/components/pi_hole/* @fabaff homeassistant/components/pi_hole/* @fabaff
homeassistant/components/plaato/* @JohNan homeassistant/components/plaato/* @JohNan
homeassistant/components/plant/* @ChristianKuehnel homeassistant/components/plant/* @ChristianKuehnel
homeassistant/components/plex/* @jjlawren
homeassistant/components/plugwise/* @laetificat @CoMPaTech homeassistant/components/plugwise/* @laetificat @CoMPaTech
homeassistant/components/point/* @fredrike homeassistant/components/point/* @fredrike
homeassistant/components/ps4/* @ktnrg45 homeassistant/components/ps4/* @ktnrg45
@ -232,6 +242,7 @@ homeassistant/components/shell_command/* @home-assistant/core
homeassistant/components/shiftr/* @fabaff homeassistant/components/shiftr/* @fabaff
homeassistant/components/shodan/* @fabaff homeassistant/components/shodan/* @fabaff
homeassistant/components/simplisafe/* @bachya homeassistant/components/simplisafe/* @bachya
homeassistant/components/slide/* @ualex73
homeassistant/components/sma/* @kellerza homeassistant/components/sma/* @kellerza
homeassistant/components/smarthab/* @outadoc homeassistant/components/smarthab/* @outadoc
homeassistant/components/smartthings/* @andrewsayre homeassistant/components/smartthings/* @andrewsayre
@ -281,15 +292,18 @@ homeassistant/components/twentemilieu/* @frenck
homeassistant/components/twilio_call/* @robbiet480 homeassistant/components/twilio_call/* @robbiet480
homeassistant/components/twilio_sms/* @robbiet480 homeassistant/components/twilio_sms/* @robbiet480
homeassistant/components/unifi/* @kane610 homeassistant/components/unifi/* @kane610
homeassistant/components/upc_connect/* @pvizeli
homeassistant/components/upcloud/* @scop homeassistant/components/upcloud/* @scop
homeassistant/components/updater/* @home-assistant/core homeassistant/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @robbiet480 homeassistant/components/upnp/* @robbiet480
homeassistant/components/uptimerobot/* @ludeeus homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @cereal2nd homeassistant/components/velbus/* @cereal2nd
homeassistant/components/velux/* @Julius2342 homeassistant/components/velux/* @Julius2342
homeassistant/components/version/* @fabaff homeassistant/components/version/* @fabaff
homeassistant/components/vesync/* @markperdue @webdjoe homeassistant/components/vesync/* @markperdue @webdjoe
homeassistant/components/vicare/* @oischinger
homeassistant/components/vizio/* @raman325 homeassistant/components/vizio/* @raman325
homeassistant/components/vlc_telnet/* @rodripf homeassistant/components/vlc_telnet/* @rodripf
homeassistant/components/waqi/* @andrey-git homeassistant/components/waqi/* @andrey-git
@ -298,6 +312,7 @@ homeassistant/components/weather/* @fabaff
homeassistant/components/weblink/* @home-assistant/core homeassistant/components/weblink/* @home-assistant/core
homeassistant/components/websocket_api/* @home-assistant/core homeassistant/components/websocket_api/* @home-assistant/core
homeassistant/components/wemo/* @sqldiablo homeassistant/components/wemo/* @sqldiablo
homeassistant/components/withings/* @vangorra
homeassistant/components/worldclock/* @fabaff homeassistant/components/worldclock/* @fabaff
homeassistant/components/wwlln/* @bachya homeassistant/components/wwlln/* @bachya
homeassistant/components/xfinity/* @cisasteelersfan homeassistant/components/xfinity/* @cisasteelersfan

View File

@ -23,9 +23,10 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
WORKDIR /workspaces WORKDIR /workspaces
# Install Python dependencies from requirements.txt if it exists # Install Python dependencies from requirements
COPY requirements_test_all.txt homeassistant/package_constraints.txt /workspaces/ COPY requirements_test.txt homeassistant/package_constraints.txt ./
RUN pip3 install -r requirements_test_all.txt -c package_constraints.txt RUN pip3 install -r requirements_test.txt -c package_constraints.txt \
&& rm -f requirements_test.txt package_constraints.txt
# Set the default shell to bash instead of sh # Set the default shell to bash instead of sh
ENV SHELL /bin/bash ENV SHELL /bin/bash

View File

@ -113,7 +113,7 @@ stages:
pip uninstall -y typing pip uninstall -y typing
- script: | - script: |
. venv/bin/activate . venv/bin/activate
pytest --timeout=9 --durations=10 --junitxml=test-results.xml -qq -o console_output_style=count -p no:sugar tests pytest --timeout=9 --durations=10 -qq -o console_output_style=count -p no:sugar tests
script/check_dirty script/check_dirty
displayName: 'Run pytest for python $(python.container)' displayName: 'Run pytest for python $(python.container)'
condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain'])) condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain']))
@ -121,22 +121,11 @@ stages:
set -e set -e
. venv/bin/activate . venv/bin/activate
pytest --timeout=9 --durations=10 --junitxml=test-results.xml --cov --cov-report=xml -qq -o console_output_style=count -p no:sugar tests pytest --timeout=9 --durations=10 --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
codecov --token $(codecovToken) codecov --token $(codecovToken)
script/check_dirty script/check_dirty
displayName: 'Run pytest for python $(python.container) / coverage' displayName: 'Run pytest for python $(python.container) / coverage'
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain'])) condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFiles: 'test-results.xml'
testRunTitle: 'Publish test results for Python $(python.container)'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: cobertura
summaryFileLocation: coverage.xml
displayName: 'publish coverage artifact'
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
- stage: 'FullCheck' - stage: 'FullCheck'
dependsOn: dependsOn:

View File

@ -43,7 +43,7 @@ stages:
release="$(Build.SourceBranchName)" release="$(Build.SourceBranchName)"
created_by="$(curl -s https://api.github.com/repos/home-assistant/home-assistant/releases/tags/${release} | jq --raw-output '.author.login')" created_by="$(curl -s https://api.github.com/repos/home-assistant/home-assistant/releases/tags/${release} | jq --raw-output '.author.login')"
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480)$ ]]; then if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480|bramkragten)$ ]]; then
exit 0 exit 0
fi fi

View File

@ -0,0 +1,66 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
branches:
include:
- dev
pr: none
schedules:
- cron: "30 0 * * *"
displayName: "translation update"
branches:
include:
- dev
always: true
variables:
- group: translation
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
jobs:
- job: 'Upload'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
inputs:
versionSpec: '3.7'
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
./script/translations_upload
displayName: 'Upload Translation'
- job: 'Download'
dependsOn:
- 'Upload'
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
inputs:
versionSpec: '3.7'
- template: templates/azp-step-git-init.yaml@azure
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
./script/translations_download
displayName: 'Download Translation'
- script: |
git checkout dev
git add homeassistant
git commit -am "[ci skip] Translation update"
git push
displayName: 'Update translation'

View File

@ -10,7 +10,7 @@ trigger:
- requirements_all.txt - requirements_all.txt
pr: none pr: none
schedules: schedules:
- cron: '0 */8 * * *' - cron: '0 */4 * * *'
displayName: 'daily builds' displayName: 'daily builds'
branches: branches:
include: include:
@ -30,7 +30,8 @@ jobs:
- template: templates/azp-job-wheels.yaml@azure - template: templates/azp-job-wheels.yaml@azure
parameters: parameters:
builderVersion: '$(versionWheels)' builderVersion: '$(versionWheels)'
builderApk: 'build-base;cmake;git;linux-headers;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;linux-headers;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev' builderApk: 'build-base;cmake;git;linux-headers;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'
builderPip: 'Cython;numpy'
wheelsRequirement: 'requirements_wheels.txt' wheelsRequirement: 'requirements_wheels.txt'
wheelsRequirementDiff: 'requirements_diff.txt' wheelsRequirementDiff: 'requirements_diff.txt'
preBuild: preBuild:
@ -65,5 +66,6 @@ jobs:
sed -i "s|# PySwitchbot|PySwitchbot|g" ${requirement_file} sed -i "s|# PySwitchbot|PySwitchbot|g" ${requirement_file}
sed -i "s|# pySwitchmate|pySwitchmate|g" ${requirement_file} sed -i "s|# pySwitchmate|pySwitchmate|g" ${requirement_file}
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file} sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
sed -i "s|# py_noaa|py_noaa|g" ${requirement_file}
done done
displayName: 'Prepare requirements files for Hass.io' displayName: 'Prepare requirements files for Hass.io'

View File

@ -7,7 +7,7 @@ import platform
import subprocess import subprocess
import sys import sys
import threading import threading
from typing import List, Dict, Any, TYPE_CHECKING # noqa pylint: disable=unused-import from typing import List, Dict, Any, TYPE_CHECKING
from homeassistant import monkey_patch from homeassistant import monkey_patch
from homeassistant.const import __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE from homeassistant.const import __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE
@ -168,7 +168,7 @@ def get_arguments() -> argparse.Namespace:
parser.add_argument( parser.add_argument(
"--runner", "--runner",
action="store_true", action="store_true",
help="On restart exit with code {}".format(RESTART_EXIT_CODE), help=f"On restart exit with code {RESTART_EXIT_CODE}",
) )
parser.add_argument( parser.add_argument(
"--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts" "--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts"
@ -216,7 +216,7 @@ def check_pid(pid_file: str) -> None:
try: try:
with open(pid_file, "r") as file: with open(pid_file, "r") as file:
pid = int(file.readline()) pid = int(file.readline())
except IOError: except OSError:
# PID File does not exist # PID File does not exist
return return
@ -239,8 +239,8 @@ def write_pid(pid_file: str) -> None:
try: try:
with open(pid_file, "w") as file: with open(pid_file, "w") as file:
file.write(str(pid)) file.write(str(pid))
except IOError: except OSError:
print("Fatal Error: Unable to write pid file {}".format(pid_file)) print(f"Fatal Error: Unable to write pid file {pid_file}")
sys.exit(1) sys.exit(1)
@ -258,7 +258,7 @@ def closefds_osx(min_fd: int, max_fd: int) -> None:
val = fcntl(_fd, F_GETFD) val = fcntl(_fd, F_GETFD)
if not val & FD_CLOEXEC: if not val & FD_CLOEXEC:
fcntl(_fd, F_SETFD, val | FD_CLOEXEC) fcntl(_fd, F_SETFD, val | FD_CLOEXEC)
except IOError: except OSError:
pass pass
@ -280,7 +280,7 @@ async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
hass = core.HomeAssistant() hass = core.HomeAssistant()
if args.demo_mode: if args.demo_mode:
config = {"frontend": {}, "demo": {}} # type: Dict[str, Any] config: Dict[str, Any] = {"frontend": {}, "demo": {}}
bootstrap.async_from_config_dict( bootstrap.async_from_config_dict(
config, config,
hass, hass,
@ -326,7 +326,7 @@ def try_to_restart() -> None:
thread.is_alive() and not thread.daemon for thread in threading.enumerate() thread.is_alive() and not thread.daemon for thread in threading.enumerate()
) )
if nthreads > 1: if nthreads > 1:
sys.stderr.write("Found {} non-daemonic threads.\n".format(nthreads)) sys.stderr.write(f"Found {nthreads} non-daemonic threads.\n")
# Somehow we sometimes seem to trigger an assertion in the python threading # Somehow we sometimes seem to trigger an assertion in the python threading
# module. It seems we find threads that have no associated OS level thread # module. It seems we find threads that have no associated OS level thread

View File

@ -47,7 +47,7 @@ async def auth_manager_from_config(
else: else:
providers = () providers = ()
# So returned auth providers are in same order as config # So returned auth providers are in same order as config
provider_hash = OrderedDict() # type: _ProviderDict provider_hash: _ProviderDict = OrderedDict()
for provider in providers: for provider in providers:
key = (provider.type, provider.id) key = (provider.type, provider.id)
provider_hash[key] = provider provider_hash[key] = provider
@ -59,7 +59,7 @@ async def auth_manager_from_config(
else: else:
modules = () modules = ()
# So returned auth modules are in same order as config # So returned auth modules are in same order as config
module_hash = OrderedDict() # type: _MfaModuleDict module_hash: _MfaModuleDict = OrderedDict()
for module in modules: for module in modules:
module_hash[module.id] = module module_hash[module.id] = module
@ -168,11 +168,11 @@ class AuthManager:
async def async_create_user(self, name: str) -> models.User: async def async_create_user(self, name: str) -> models.User:
"""Create a user.""" """Create a user."""
kwargs = { kwargs: Dict[str, Any] = {
"name": name, "name": name,
"is_active": True, "is_active": True,
"group_ids": [GROUP_ID_ADMIN], "group_ids": [GROUP_ID_ADMIN],
} # type: Dict[str, Any] }
if await self._user_should_be_owner(): if await self._user_should_be_owner():
kwargs["is_owner"] = True kwargs["is_owner"] = True
@ -238,7 +238,7 @@ class AuthManager:
group_ids: Optional[List[str]] = None, group_ids: Optional[List[str]] = None,
) -> None: ) -> None:
"""Update a user.""" """Update a user."""
kwargs = {} # type: Dict[str,Any] kwargs: Dict[str, Any] = {}
if name is not None: if name is not None:
kwargs["name"] = name kwargs["name"] = name
if group_ids is not None: if group_ids is not None:
@ -278,9 +278,7 @@ class AuthManager:
module = self.get_auth_mfa_module(mfa_module_id) module = self.get_auth_mfa_module(mfa_module_id)
if module is None: if module is None:
raise ValueError( raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")
"Unable find multi-factor auth module: {}".format(mfa_module_id)
)
await module.async_setup_user(user.id, data) await module.async_setup_user(user.id, data)
@ -295,15 +293,13 @@ class AuthManager:
module = self.get_auth_mfa_module(mfa_module_id) module = self.get_auth_mfa_module(mfa_module_id)
if module is None: if module is None:
raise ValueError( raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")
"Unable find multi-factor auth module: {}".format(mfa_module_id)
)
await module.async_depose_user(user.id) await module.async_depose_user(user.id)
async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]: async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]:
"""List enabled mfa modules for user.""" """List enabled mfa modules for user."""
modules = OrderedDict() # type: Dict[str, str] modules: Dict[str, str] = OrderedDict()
for module_id, module in self._mfa_modules.items(): for module_id, module in self._mfa_modules.items():
if await module.async_is_user_setup(user.id): if await module.async_is_user_setup(user.id):
modules[module_id] = module.name modules[module_id] = module.name
@ -356,7 +352,7 @@ class AuthManager:
): ):
# Each client_name can only have one # Each client_name can only have one
# long_lived_access_token type of refresh token # long_lived_access_token type of refresh token
raise ValueError("{} already exists".format(client_name)) raise ValueError(f"{client_name} already exists")
return await self._store.async_create_refresh_token( return await self._store.async_create_refresh_token(
user, user,

View File

@ -4,7 +4,7 @@ from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
import hmac import hmac
from logging import getLogger from logging import getLogger
from typing import Any, Dict, List, Optional # noqa: F401 from typing import Any, Dict, List, Optional
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
@ -13,7 +13,7 @@ from homeassistant.util import dt as dt_util
from . import models from . import models
from .const import GROUP_ID_ADMIN, GROUP_ID_USER, GROUP_ID_READ_ONLY from .const import GROUP_ID_ADMIN, GROUP_ID_USER, GROUP_ID_READ_ONLY
from .permissions import PermissionLookup, system_policies from .permissions import PermissionLookup, system_policies
from .permissions.types import PolicyType # noqa: F401 from .permissions.types import PolicyType
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth" STORAGE_KEY = "auth"
@ -34,9 +34,9 @@ class AuthStore:
def __init__(self, hass: HomeAssistant) -> None: def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the auth store.""" """Initialize the auth store."""
self.hass = hass self.hass = hass
self._users = None # type: Optional[Dict[str, models.User]] self._users: Optional[Dict[str, models.User]] = None
self._groups = None # type: Optional[Dict[str, models.Group]] self._groups: Optional[Dict[str, models.Group]] = None
self._perm_lookup = None # type: Optional[PermissionLookup] self._perm_lookup: Optional[PermissionLookup] = None
self._store = hass.helpers.storage.Store( self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True STORAGE_VERSION, STORAGE_KEY, private=True
) )
@ -94,16 +94,16 @@ class AuthStore:
for group_id in group_ids or []: for group_id in group_ids or []:
group = self._groups.get(group_id) group = self._groups.get(group_id)
if group is None: if group is None:
raise ValueError("Invalid group specified {}".format(group_id)) raise ValueError(f"Invalid group specified {group_id}")
groups.append(group) groups.append(group)
kwargs = { kwargs: Dict[str, Any] = {
"name": name, "name": name,
# Until we get group management, we just put everyone in the # Until we get group management, we just put everyone in the
# same group. # same group.
"groups": groups, "groups": groups,
"perm_lookup": self._perm_lookup, "perm_lookup": self._perm_lookup,
} # type: Dict[str, Any] }
if is_owner is not None: if is_owner is not None:
kwargs["is_owner"] = is_owner kwargs["is_owner"] = is_owner
@ -210,12 +210,12 @@ class AuthStore:
access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION, access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION,
) -> models.RefreshToken: ) -> models.RefreshToken:
"""Create a new token for a user.""" """Create a new token for a user."""
kwargs = { kwargs: Dict[str, Any] = {
"user": user, "user": user,
"client_id": client_id, "client_id": client_id,
"token_type": token_type, "token_type": token_type,
"access_token_expiration": access_token_expiration, "access_token_expiration": access_token_expiration,
} # type: Dict[str, Any] }
if client_name: if client_name:
kwargs["client_name"] = client_name kwargs["client_name"] = client_name
if client_icon: if client_icon:
@ -307,8 +307,8 @@ class AuthStore:
self._set_defaults() self._set_defaults()
return return
users = OrderedDict() # type: Dict[str, models.User] users: Dict[str, models.User] = OrderedDict()
groups = OrderedDict() # type: Dict[str, models.Group] groups: Dict[str, models.Group] = OrderedDict()
# Soft-migrating data as we load. We are going to make sure we have a # Soft-migrating data as we load. We are going to make sure we have a
# read only group and an admin group. There are two states that we can # read only group and an admin group. There are two states that we can
@ -325,7 +325,7 @@ class AuthStore:
# was added. # was added.
for group_dict in data.get("groups", []): for group_dict in data.get("groups", []):
policy = None # type: Optional[PolicyType] policy: Optional[PolicyType] = None
if group_dict["id"] == GROUP_ID_ADMIN: if group_dict["id"] == GROUP_ID_ADMIN:
has_admin_group = True has_admin_group = True
@ -503,11 +503,11 @@ class AuthStore:
groups = [] groups = []
for group in self._groups.values(): for group in self._groups.values():
g_dict = { g_dict: Dict[str, Any] = {
"id": group.id, "id": group.id,
# Name not read for sys groups. Kept here for backwards compat # Name not read for sys groups. Kept here for backwards compat
"name": group.name, "name": group.name,
} # type: Dict[str, Any] }
if not group.system_generated: if not group.system_generated:
g_dict["policy"] = group.policy g_dict["policy"] = group.policy
@ -558,7 +558,7 @@ class AuthStore:
"""Set default values for auth store.""" """Set default values for auth store."""
self._users = OrderedDict() self._users = OrderedDict()
groups = OrderedDict() # type: Dict[str, models.Group] groups: Dict[str, models.Group] = OrderedDict()
admin_group = _system_admin_group() admin_group = _system_admin_group()
groups[admin_group.id] = admin_group groups[admin_group.id] = admin_group
user_group = _system_user_group() user_group = _system_user_group()

View File

@ -109,7 +109,7 @@ class SetupFlow(data_entry_flow.FlowHandler):
Return self.async_show_form(step_id='init') if user_input is None. Return self.async_show_form(step_id='init') if user_input is None.
Return self.async_create_entry(data={'result': result}) if finish. Return self.async_create_entry(data={'result': result}) if finish.
""" """
errors = {} # type: Dict[str, str] errors: Dict[str, str] = {}
if user_input: if user_input:
result = await self._auth_module.async_setup_user(self._user_id, user_input) result = await self._auth_module.async_setup_user(self._user_id, user_input)
@ -144,15 +144,13 @@ async def auth_mfa_module_from_config(
async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType: async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType:
"""Load an mfa auth module.""" """Load an mfa auth module."""
module_path = "homeassistant.auth.mfa_modules.{}".format(module_name) module_path = f"homeassistant.auth.mfa_modules.{module_name}"
try: try:
module = importlib.import_module(module_path) module = importlib.import_module(module_path)
except ImportError as err: except ImportError as err:
_LOGGER.error("Unable to load mfa module %s: %s", module_name, err) _LOGGER.error("Unable to load mfa module %s: %s", module_name, err)
raise HomeAssistantError( raise HomeAssistantError(f"Unable to load mfa module {module_name}: {err}")
"Unable to load mfa module {}: {}".format(module_name, err)
)
if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"): if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"):
return module return module

View File

@ -95,7 +95,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
"""Initialize the user data store.""" """Initialize the user data store."""
super().__init__(hass, config) super().__init__(hass, config)
self._user_settings = None # type: Optional[_UsersDict] self._user_settings: Optional[_UsersDict] = None
self._user_store = hass.helpers.storage.Store( self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True STORAGE_VERSION, STORAGE_KEY, private=True
) )
@ -279,18 +279,18 @@ class NotifySetupFlow(SetupFlow):
"""Initialize the setup flow.""" """Initialize the setup flow."""
super().__init__(auth_module, setup_schema, user_id) super().__init__(auth_module, setup_schema, user_id)
# to fix typing complaint # to fix typing complaint
self._auth_module = auth_module # type: NotifyAuthModule self._auth_module: NotifyAuthModule = auth_module
self._available_notify_services = available_notify_services self._available_notify_services = available_notify_services
self._secret = None # type: Optional[str] self._secret: Optional[str] = None
self._count = None # type: Optional[int] self._count: Optional[int] = None
self._notify_service = None # type: Optional[str] self._notify_service: Optional[str] = None
self._target = None # type: Optional[str] self._target: Optional[str] = None
async def async_step_init( async def async_step_init(
self, user_input: Optional[Dict[str, str]] = None self, user_input: Optional[Dict[str, str]] = None
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Let user select available notify services.""" """Let user select available notify services."""
errors = {} # type: Dict[str, str] errors: Dict[str, str] = {}
hass = self._auth_module.hass hass = self._auth_module.hass
if user_input: if user_input:
@ -304,7 +304,7 @@ class NotifySetupFlow(SetupFlow):
if not self._available_notify_services: if not self._available_notify_services:
return self.async_abort(reason="no_available_service") return self.async_abort(reason="no_available_service")
schema = OrderedDict() # type: Dict[str, Any] schema: Dict[str, Any] = OrderedDict()
schema["notify_service"] = vol.In(self._available_notify_services) schema["notify_service"] = vol.In(self._available_notify_services)
schema["target"] = vol.Optional(str) schema["target"] = vol.Optional(str)
@ -316,7 +316,7 @@ class NotifySetupFlow(SetupFlow):
self, user_input: Optional[Dict[str, str]] = None self, user_input: Optional[Dict[str, str]] = None
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Verify user can recevie one-time password.""" """Verify user can recevie one-time password."""
errors = {} # type: Dict[str, str] errors: Dict[str, str] = {}
hass = self._auth_module.hass hass = self._auth_module.hass
if user_input: if user_input:

View File

@ -2,7 +2,7 @@
import asyncio import asyncio
import logging import logging
from io import BytesIO from io import BytesIO
from typing import Any, Dict, Optional, Tuple # noqa: F401 from typing import Any, Dict, Optional, Tuple
import voluptuous as vol import voluptuous as vol
@ -75,7 +75,7 @@ class TotpAuthModule(MultiFactorAuthModule):
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
"""Initialize the user data store.""" """Initialize the user data store."""
super().__init__(hass, config) super().__init__(hass, config)
self._users = None # type: Optional[Dict[str, str]] self._users: Optional[Dict[str, str]] = None
self._user_store = hass.helpers.storage.Store( self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True STORAGE_VERSION, STORAGE_KEY, private=True
) )
@ -107,7 +107,7 @@ class TotpAuthModule(MultiFactorAuthModule):
"""Create a ota_secret for user.""" """Create a ota_secret for user."""
import pyotp import pyotp
ota_secret = secret or pyotp.random_base32() # type: str ota_secret: str = secret or pyotp.random_base32()
self._users[user_id] = ota_secret # type: ignore self._users[user_id] = ota_secret # type: ignore
return ota_secret return ota_secret
@ -181,9 +181,9 @@ class TotpSetupFlow(SetupFlow):
"""Initialize the setup flow.""" """Initialize the setup flow."""
super().__init__(auth_module, setup_schema, user.id) super().__init__(auth_module, setup_schema, user.id)
# to fix typing complaint # to fix typing complaint
self._auth_module = auth_module # type: TotpAuthModule self._auth_module: TotpAuthModule = auth_module
self._user = user self._user = user
self._ota_secret = None # type: Optional[str] self._ota_secret: Optional[str] = None
self._url = None # type Optional[str] self._url = None # type Optional[str]
self._image = None # type Optional[str] self._image = None # type Optional[str]
@ -197,7 +197,7 @@ class TotpSetupFlow(SetupFlow):
""" """
import pyotp import pyotp
errors = {} # type: Dict[str, str] errors: Dict[str, str] = {}
if user_input: if user_input:
verified = await self.hass.async_add_executor_job( # type: ignore verified = await self.hass.async_add_executor_job( # type: ignore

View File

@ -1,6 +1,6 @@
"""Auth models.""" """Auth models."""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict, List, NamedTuple, Optional # noqa: F401 from typing import Dict, List, NamedTuple, Optional
import uuid import uuid
import attr import attr
@ -20,7 +20,7 @@ TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token"
class Group: class Group:
"""A group.""" """A group."""
name = attr.ib(type=str) # type: Optional[str] name = attr.ib(type=Optional[str])
policy = attr.ib(type=perm_mdl.PolicyType) policy = attr.ib(type=perm_mdl.PolicyType)
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
system_generated = attr.ib(type=bool, default=False) system_generated = attr.ib(type=bool, default=False)
@ -30,24 +30,20 @@ class Group:
class User: class User:
"""A user.""" """A user."""
name = attr.ib(type=str) # type: Optional[str] name = attr.ib(type=Optional[str])
perm_lookup = attr.ib( perm_lookup = attr.ib(type=perm_mdl.PermissionLookup, cmp=False)
type=perm_mdl.PermissionLookup, cmp=False
) # type: perm_mdl.PermissionLookup
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
is_owner = attr.ib(type=bool, default=False) is_owner = attr.ib(type=bool, default=False)
is_active = attr.ib(type=bool, default=False) is_active = attr.ib(type=bool, default=False)
system_generated = attr.ib(type=bool, default=False) system_generated = attr.ib(type=bool, default=False)
groups = attr.ib(type=List, factory=list, cmp=False) # type: List[Group] groups = attr.ib(type=List[Group], factory=list, cmp=False)
# List of credentials of a user. # List of credentials of a user.
credentials = attr.ib(type=list, factory=list, cmp=False) # type: List[Credentials] credentials = attr.ib(type=List["Credentials"], factory=list, cmp=False)
# Tokens associated with a user. # Tokens associated with a user.
refresh_tokens = attr.ib( refresh_tokens = attr.ib(type=Dict[str, "RefreshToken"], factory=dict, cmp=False)
type=dict, factory=dict, cmp=False
) # type: Dict[str, RefreshToken]
_permissions = attr.ib( _permissions = attr.ib(
type=Optional[perm_mdl.PolicyPermissions], init=False, cmp=False, default=None type=Optional[perm_mdl.PolicyPermissions], init=False, cmp=False, default=None

View File

@ -1,6 +1,6 @@
"""Entity permissions.""" """Entity permissions."""
from collections import OrderedDict from collections import OrderedDict
from typing import Callable, Optional # noqa: F401 from typing import Callable, Optional
import voluptuous as vol import voluptuous as vol
@ -8,8 +8,7 @@ from .const import SUBCAT_ALL, POLICY_READ, POLICY_CONTROL, POLICY_EDIT
from .models import PermissionLookup from .models import PermissionLookup
from .types import CategoryType, SubCategoryDict, ValueType from .types import CategoryType, SubCategoryDict, ValueType
# pylint: disable=unused-import from .util import SubCatLookupType, lookup_all, compile_policy
from .util import SubCatLookupType, lookup_all, compile_policy # noqa
SINGLE_ENTITY_SCHEMA = vol.Any( SINGLE_ENTITY_SCHEMA = vol.Any(
True, True,
@ -90,7 +89,7 @@ def compile_entities(
policy: CategoryType, perm_lookup: PermissionLookup policy: CategoryType, perm_lookup: PermissionLookup
) -> Callable[[str, str], bool]: ) -> Callable[[str, str], bool]:
"""Compile policy into a function that tests policy.""" """Compile policy into a function that tests policy."""
subcategories = OrderedDict() # type: SubCatLookupType subcategories: SubCatLookupType = OrderedDict()
subcategories[ENTITY_ENTITY_IDS] = _lookup_entity_id subcategories[ENTITY_ENTITY_IDS] = _lookup_entity_id
subcategories[ENTITY_DEVICE_IDS] = _lookup_device subcategories[ENTITY_DEVICE_IDS] = _lookup_device
subcategories[ENTITY_AREAS] = _lookup_area subcategories[ENTITY_AREAS] = _lookup_area

View File

@ -1,13 +1,13 @@
"""Merging of policies.""" """Merging of policies."""
from typing import cast, Dict, List, Set # noqa: F401 from typing import cast, Dict, List, Set
from .types import PolicyType, CategoryType from .types import PolicyType, CategoryType
def merge_policies(policies: List[PolicyType]) -> PolicyType: def merge_policies(policies: List[PolicyType]) -> PolicyType:
"""Merge policies.""" """Merge policies."""
new_policy = {} # type: Dict[str, CategoryType] new_policy: Dict[str, CategoryType] = {}
seen = set() # type: Set[str] seen: Set[str] = set()
for policy in policies: for policy in policies:
for category in policy: for category in policy:
if category in seen: if category in seen:
@ -33,8 +33,8 @@ def _merge_policies(sources: List[CategoryType]) -> CategoryType:
# If there are multiple sources with a dict as policy, we recursively # If there are multiple sources with a dict as policy, we recursively
# merge each key in the source. # merge each key in the source.
policy = None # type: CategoryType policy: CategoryType = None
seen = set() # type: Set[str] seen: Set[str] = set()
for source in sources: for source in sources:
if source is None: if source is None:
continue continue

View File

@ -1,7 +1,7 @@
"""Helpers to deal with permissions.""" """Helpers to deal with permissions."""
from functools import wraps from functools import wraps
from typing import Callable, Dict, List, Optional, cast # noqa: F401 from typing import Callable, Dict, List, Optional, cast
from .const import SUBCAT_ALL from .const import SUBCAT_ALL
from .models import PermissionLookup from .models import PermissionLookup
@ -45,7 +45,7 @@ def compile_policy(
assert isinstance(policy, dict) assert isinstance(policy, dict)
funcs = [] # type: List[Callable[[str, str], Optional[bool]]] funcs: List[Callable[[str, str], Optional[bool]]] = []
for key, lookup_func in subcategories.items(): for key, lookup_func in subcategories.items():
lookup_value = policy.get(key) lookup_value = policy.get(key)
@ -85,7 +85,7 @@ def _gen_dict_test_func(
def test_value(object_id: str, key: str) -> Optional[bool]: def test_value(object_id: str, key: str) -> Optional[bool]:
"""Test if permission is allowed based on the keys.""" """Test if permission is allowed based on the keys."""
schema = lookup_func(perm_lookup, lookup_dict, object_id) # type: ValueType schema: ValueType = lookup_func(perm_lookup, lookup_dict, object_id)
if schema is None or isinstance(schema, bool): if schema is None or isinstance(schema, bool):
return schema return schema

View File

@ -16,7 +16,7 @@ from homeassistant.util.decorator import Registry
from ..auth_store import AuthStore from ..auth_store import AuthStore
from ..const import MFA_SESSION_EXPIRATION from ..const import MFA_SESSION_EXPIRATION
from ..models import Credentials, User, UserMeta # noqa: F401 from ..models import Credentials, User, UserMeta
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_REQS = "auth_prov_reqs_processed" DATA_REQS = "auth_prov_reqs_processed"
@ -144,14 +144,10 @@ async def load_auth_provider_module(
) -> types.ModuleType: ) -> types.ModuleType:
"""Load an auth provider.""" """Load an auth provider."""
try: try:
module = importlib.import_module( module = importlib.import_module(f"homeassistant.auth.providers.{provider}")
"homeassistant.auth.providers.{}".format(provider)
)
except ImportError as err: except ImportError as err:
_LOGGER.error("Unable to load auth provider %s: %s", provider, err) _LOGGER.error("Unable to load auth provider %s: %s", provider, err)
raise HomeAssistantError( raise HomeAssistantError(f"Unable to load auth provider {provider}: {err}")
"Unable to load auth provider {}: {}".format(provider, err)
)
if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"): if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"):
return module return module
@ -166,7 +162,7 @@ async def load_auth_provider_module(
# https://github.com/python/mypy/issues/1424 # https://github.com/python/mypy/issues/1424
reqs = module.REQUIREMENTS # type: ignore reqs = module.REQUIREMENTS # type: ignore
await requirements.async_process_requirements( await requirements.async_process_requirements(
hass, "auth provider {}".format(provider), reqs hass, f"auth provider {provider}", reqs
) )
processed.add(provider) processed.add(provider)
@ -179,12 +175,12 @@ class LoginFlow(data_entry_flow.FlowHandler):
def __init__(self, auth_provider: AuthProvider) -> None: def __init__(self, auth_provider: AuthProvider) -> None:
"""Initialize the login flow.""" """Initialize the login flow."""
self._auth_provider = auth_provider self._auth_provider = auth_provider
self._auth_module_id = None # type: Optional[str] self._auth_module_id: Optional[str] = None
self._auth_manager = auth_provider.hass.auth # type: ignore self._auth_manager = auth_provider.hass.auth # type: ignore
self.available_mfa_modules = {} # type: Dict[str, str] self.available_mfa_modules: Dict[str, str] = {}
self.created_at = dt_util.utcnow() self.created_at = dt_util.utcnow()
self.invalid_mfa_times = 0 self.invalid_mfa_times = 0
self.user = None # type: Optional[User] self.user: Optional[User] = None
async def async_step_init( async def async_step_init(
self, user_input: Optional[Dict[str, str]] = None self, user_input: Optional[Dict[str, str]] = None
@ -259,10 +255,10 @@ class LoginFlow(data_entry_flow.FlowHandler):
if not errors: if not errors:
return await self.async_finish(self.user) return await self.async_finish(self.user)
description_placeholders = { description_placeholders: Dict[str, Optional[str]] = {
"mfa_module_name": auth_module.name, "mfa_module_name": auth_module.name,
"mfa_module_id": auth_module.id, "mfa_module_id": auth_module.id,
} # type: Dict[str, Optional[str]] }
return self.async_show_form( return self.async_show_form(
step_id="mfa", step_id="mfa",

View File

@ -53,7 +53,7 @@ class CommandLineAuthProvider(AuthProvider):
attributes provided by external programs. attributes provided by external programs.
""" """
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._user_meta = {} # type: Dict[str, Dict[str, Any]] self._user_meta: Dict[str, Dict[str, Any]] = {}
async def async_login_flow(self, context: Optional[dict]) -> LoginFlow: async def async_login_flow(self, context: Optional[dict]) -> LoginFlow:
"""Return a flow to login.""" """Return a flow to login."""
@ -85,7 +85,7 @@ class CommandLineAuthProvider(AuthProvider):
raise InvalidAuthError raise InvalidAuthError
if self.config[CONF_META]: if self.config[CONF_META]:
meta = {} # type: Dict[str, str] meta: Dict[str, str] = {}
for _line in stdout.splitlines(): for _line in stdout.splitlines():
try: try:
line = _line.decode().lstrip() line = _line.decode().lstrip()
@ -146,7 +146,7 @@ class CommandLineLoginFlow(LoginFlow):
user_input.pop("password") user_input.pop("password")
return await self.async_finish(user_input) return await self.async_finish(user_input)
schema = collections.OrderedDict() # type: Dict[str, type] schema: Dict[str, type] = collections.OrderedDict()
schema["username"] = str schema["username"] = str
schema["password"] = str schema["password"] = str

View File

@ -4,7 +4,7 @@ import base64
from collections import OrderedDict from collections import OrderedDict
import logging import logging
from typing import Any, Dict, List, Optional, Set, cast # noqa: F401 from typing import Any, Dict, List, Optional, Set, cast
import bcrypt import bcrypt
import voluptuous as vol import voluptuous as vol
@ -53,7 +53,7 @@ class Data:
self._store = hass.helpers.storage.Store( self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True STORAGE_VERSION, STORAGE_KEY, private=True
) )
self._data = None # type: Optional[Dict[str, Any]] self._data: Optional[Dict[str, Any]] = None
# Legacy mode will allow usernames to start/end with whitespace # Legacy mode will allow usernames to start/end with whitespace
# and will compare usernames case-insensitive. # and will compare usernames case-insensitive.
# Remove in 2020 or when we launch 1.0. # Remove in 2020 or when we launch 1.0.
@ -74,7 +74,7 @@ class Data:
if data is None: if data is None:
data = {"users": []} data = {"users": []}
seen = set() # type: Set[str] seen: Set[str] = set()
for user in data["users"]: for user in data["users"]:
username = user["username"] username = user["username"]
@ -210,7 +210,7 @@ class HassAuthProvider(AuthProvider):
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize an Home Assistant auth provider.""" """Initialize an Home Assistant auth provider."""
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.data = None # type: Optional[Data] self.data: Optional[Data] = None
self._init_lock = asyncio.Lock() self._init_lock = asyncio.Lock()
async def async_initialize(self) -> None: async def async_initialize(self) -> None:
@ -296,7 +296,7 @@ class HassLoginFlow(LoginFlow):
user_input.pop("password") user_input.pop("password")
return await self.async_finish(user_input) return await self.async_finish(user_input)
schema = OrderedDict() # type: Dict[str, type] schema: Dict[str, type] = OrderedDict()
schema["username"] = str schema["username"] = str
schema["password"] = str schema["password"] = str

View File

@ -112,7 +112,7 @@ class ExampleLoginFlow(LoginFlow):
user_input.pop("password") user_input.pop("password")
return await self.async_finish(user_input) return await self.async_finish(user_input)
schema = OrderedDict() # type: Dict[str, type] schema: Dict[str, type] = OrderedDict()
schema["username"] = str schema["username"] = str
schema["password"] = str schema["password"] = str

View File

@ -97,6 +97,17 @@ async def async_from_config_dict(
stop = time() stop = time()
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start) _LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
if sys.version_info[:3] < (3, 6, 1):
msg = (
"Python 3.6.0 support is deprecated and will "
"be removed in the first release after October 2. Please "
"upgrade Python to 3.6.1 or higher."
)
_LOGGER.warning(msg)
hass.components.persistent_notification.async_create(
msg, "Python version", "python_version"
)
return hass return hass
@ -163,7 +174,7 @@ def async_enable_logging(
# ensure that the handlers it sets up wraps the correct streams. # ensure that the handlers it sets up wraps the correct streams.
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
colorfmt = "%(log_color)s{}%(reset)s".format(fmt) colorfmt = f"%(log_color)s{fmt}%(reset)s"
logging.getLogger().handlers[0].setFormatter( logging.getLogger().handlers[0].setFormatter(
ColoredFormatter( ColoredFormatter(
colorfmt, colorfmt,
@ -206,9 +217,9 @@ def async_enable_logging(
): ):
if log_rotate_days: if log_rotate_days:
err_handler = logging.handlers.TimedRotatingFileHandler( err_handler: logging.FileHandler = logging.handlers.TimedRotatingFileHandler(
err_log_path, when="midnight", backupCount=log_rotate_days err_log_path, when="midnight", backupCount=log_rotate_days
) # type: logging.FileHandler )
else: else:
err_handler = logging.FileHandler(err_log_path, mode="w", delay=True) err_handler = logging.FileHandler(err_log_path, mode="w", delay=True)
@ -335,7 +346,7 @@ async def _async_set_up_integrations(
) )
# Load all integrations # Load all integrations
after_dependencies = {} # type: Dict[str, Set[str]] after_dependencies: Dict[str, Set[str]] = {}
for int_or_exc in await asyncio.gather( for int_or_exc in await asyncio.gather(
*(loader.async_get_integration(hass, domain) for domain in stage_2_domains), *(loader.async_get_integration(hass, domain) for domain in stage_2_domains),

View File

@ -1,21 +1,30 @@
{ {
"config": { "config": {
"abort": { "abort": {
"existing_instance_updated": "Configurazione esistente aggiornata.",
"single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home." "single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home."
}, },
"error": { "error": {
"connection_error": "Impossibile connettersi." "connection_error": "Impossibile connettersi."
}, },
"step": { "step": {
"hassio_confirm": {
"description": "Vuoi configurare Home Assistant per connettersi alla AdGuard Home fornita dal componente aggiuntivo di Hass.io: {addon} ?",
"title": "AdGuard Home tramite il componente aggiuntivo di Hass.io"
},
"user": { "user": {
"data": { "data": {
"host": "Host", "host": "Host",
"password": "Password", "password": "Password",
"port": "Porta", "port": "Porta",
"ssl": "AdGuard Home utilizza un certificato SSL", "ssl": "AdGuard Home utilizza un certificato SSL",
"username": "Nome utente" "username": "Nome utente",
} "verify_ssl": "AdGuard Home utilizza un certificato appropriato"
} },
} "description": "Configura l'istanza di AdGuard Home per consentire il monitoraggio e il controllo.",
"title": "Collega la tua AdGuard Home."
}
},
"title": "AdGuard Home"
} }
} }

View File

@ -5,11 +5,11 @@
"single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home." "single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home."
}, },
"error": { "error": {
"connection_error": "Po\u0142\u0105czenie nieudane." "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia."
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {
"description": "Czy chcesz skonfigurowa\u0107 Home Assistant'a, aby po\u0142\u0105czy\u0142 si\u0119 z AdGuard Home przez dodatek Hass.io {addon}?", "description": "Czy chcesz skonfigurowa\u0107 Home Assistant, aby po\u0142\u0105czy\u0142 si\u0119 z AdGuard Home przez dodatek Hass.io {addon}?",
"title": "AdGuard Home przez dodatek Hass.io" "title": "AdGuard Home przez dodatek Hass.io"
}, },
"user": { "user": {
@ -21,7 +21,7 @@
"username": "Nazwa u\u017cytkownika", "username": "Nazwa u\u017cytkownika",
"verify_ssl": "AdGuard Home u\u017cywa odpowiedniego certyfikatu." "verify_ssl": "AdGuard Home u\u017cywa odpowiedniego certyfikatu."
}, },
"description": "Skonfiguruj swoj\u0105 instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i nadz\u00f3r sieci.", "description": "Skonfiguruj instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i kontrol\u0119.",
"title": "Po\u0142\u0105cz sw\u00f3j AdGuard Home" "title": "Po\u0142\u0105cz sw\u00f3j AdGuard Home"
} }
}, },

View File

@ -132,7 +132,7 @@ class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor):
async def _adguard_update(self) -> None: async def _adguard_update(self) -> None:
"""Update AdGuard Home entity.""" """Update AdGuard Home entity."""
percentage = await self.adguard.stats.blocked_percentage() percentage = await self.adguard.stats.blocked_percentage()
self._state = "{:.2f}".format(percentage) self._state = f"{percentage:.2f}"
class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor): class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor):
@ -205,7 +205,7 @@ class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor):
async def _adguard_update(self) -> None: async def _adguard_update(self) -> None:
"""Update AdGuard Home entity.""" """Update AdGuard Home entity."""
average = await self.adguard.stats.avg_processing_time() average = await self.adguard.stats.avg_processing_time()
self._state = "{:.2f}".format(average) self._state = f"{average:.2f}"
class AdGuardHomeRulesCountSensor(AdGuardHomeSensor): class AdGuardHomeRulesCountSensor(AdGuardHomeSensor):

View File

@ -194,7 +194,7 @@ class AirVisualSensor(Entity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique, HASS-friendly identifier for this entity.""" """Return a unique, HASS-friendly identifier for this entity."""
return "{0}_{1}_{2}".format(self._location_id, self._locale, self._type) return f"{self._location_id}_{self._locale}_{self._type}"
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
@ -210,7 +210,7 @@ class AirVisualSensor(Entity):
return return
if self._type == SENSOR_TYPE_LEVEL: if self._type == SENSOR_TYPE_LEVEL:
aqi = data["aqi{0}".format(self._locale)] aqi = data[f"aqi{self._locale}"]
[level] = [ [level] = [
i i
for i in POLLUTANT_LEVEL_MAPPING for i in POLLUTANT_LEVEL_MAPPING
@ -219,9 +219,9 @@ class AirVisualSensor(Entity):
self._state = level["label"] self._state = level["label"]
self._icon = level["icon"] self._icon = level["icon"]
elif self._type == SENSOR_TYPE_AQI: elif self._type == SENSOR_TYPE_AQI:
self._state = data["aqi{0}".format(self._locale)] self._state = data[f"aqi{self._locale}"]
elif self._type == SENSOR_TYPE_POLLUTANT: elif self._type == SENSOR_TYPE_POLLUTANT:
symbol = data["main{0}".format(self._locale)] symbol = data[f"main{self._locale}"]
self._state = POLLUTANT_MAPPING[symbol]["label"] self._state = POLLUTANT_MAPPING[symbol]["label"]
self._attrs.update( self._attrs.update(
{ {

View File

@ -85,7 +85,7 @@ class AladdinDevice(CoverDevice):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique ID.""" """Return a unique ID."""
return "{}-{}".format(self._device_id, self._number) return f"{self._device_id}-{self._number}"
@property @property
def name(self): def name(self):

View File

@ -13,13 +13,17 @@ from homeassistant.const import (
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from . import DATA_AD, SIGNAL_PANEL_MESSAGE from . import DATA_AD, DOMAIN as DOMAIN_ALARMDECODER, SIGNAL_PANEL_MESSAGE
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SERVICE_ALARM_TOGGLE_CHIME = "alarmdecoder_alarm_toggle_chime" SERVICE_ALARM_TOGGLE_CHIME = "alarmdecoder_alarm_toggle_chime"
ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({vol.Required(ATTR_CODE): cv.string}) ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({vol.Required(ATTR_CODE): cv.string})
SERVICE_ALARM_KEYPRESS = "alarm_keypress"
ATTR_KEYPRESS = "keypress"
ALARM_KEYPRESS_SCHEMA = vol.Schema({vol.Required(ATTR_KEYPRESS): cv.string})
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up for AlarmDecoder alarm panels.""" """Set up for AlarmDecoder alarm panels."""
@ -38,6 +42,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
schema=ALARM_TOGGLE_CHIME_SCHEMA, schema=ALARM_TOGGLE_CHIME_SCHEMA,
) )
def alarm_keypress_handler(service):
"""Register keypress handler."""
keypress = service.data[ATTR_KEYPRESS]
device.alarm_keypress(keypress)
hass.services.register(
DOMAIN_ALARMDECODER,
SERVICE_ALARM_KEYPRESS,
alarm_keypress_handler,
schema=ALARM_KEYPRESS_SCHEMA,
)
class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel): class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
"""Representation of an AlarmDecoder-based alarm panel.""" """Representation of an AlarmDecoder-based alarm panel."""
@ -124,24 +140,29 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
def alarm_disarm(self, code=None): def alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
if code: if code:
self.hass.data[DATA_AD].send("{!s}1".format(code)) self.hass.data[DATA_AD].send(f"{code!s}1")
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code=None):
"""Send arm away command.""" """Send arm away command."""
if code: if code:
self.hass.data[DATA_AD].send("{!s}2".format(code)) self.hass.data[DATA_AD].send(f"{code!s}2")
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code=None):
"""Send arm home command.""" """Send arm home command."""
if code: if code:
self.hass.data[DATA_AD].send("{!s}3".format(code)) self.hass.data[DATA_AD].send(f"{code!s}3")
def alarm_arm_night(self, code=None): def alarm_arm_night(self, code=None):
"""Send arm night command.""" """Send arm night command."""
if code: if code:
self.hass.data[DATA_AD].send("{!s}33".format(code)) self.hass.data[DATA_AD].send(f"{code!s}33")
def alarm_toggle_chime(self, code=None): def alarm_toggle_chime(self, code=None):
"""Send toggle chime command.""" """Send toggle chime command."""
if code: if code:
self.hass.data[DATA_AD].send("{!s}9".format(code)) self.hass.data[DATA_AD].send(f"{code!s}9")
def alarm_keypress(self, keypress):
"""Send custom keypresses."""
if keypress:
self.hass.data[DATA_AD].send(keypress)

View File

@ -0,0 +1,9 @@
alarm_keypress:
description: Send custom keypresses to the alarm.
fields:
entity_id:
description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs'
keypress:
description: 'String to send to the alarm panel.'
example: '*71'

View File

@ -40,7 +40,7 @@ class AlexaInvalidEndpointError(AlexaError):
def __init__(self, endpoint_id): def __init__(self, endpoint_id):
"""Initialize invalid endpoint error.""" """Initialize invalid endpoint error."""
msg = "The endpoint {} does not exist".format(endpoint_id) msg = f"The endpoint {endpoint_id} does not exist"
AlexaError.__init__(self, msg) AlexaError.__init__(self, msg)
self.endpoint_id = endpoint_id self.endpoint_id = endpoint_id
@ -73,7 +73,7 @@ class AlexaTempRangeError(AlexaError):
"maximumValue": {"value": max_temp, "scale": API_TEMP_UNITS[unit]}, "maximumValue": {"value": max_temp, "scale": API_TEMP_UNITS[unit]},
} }
payload = {"validRange": temp_range} payload = {"validRange": temp_range}
msg = "The requested temperature {} is out of range".format(temp) msg = f"The requested temperature {temp} is out of range"
AlexaError.__init__(self, msg, payload) AlexaError.__init__(self, msg, payload)

View File

@ -744,7 +744,7 @@ async def async_api_set_thermostat_mode(hass, config, directive, context):
presets = entity.attributes.get(climate.ATTR_PRESET_MODES, []) presets = entity.attributes.get(climate.ATTR_PRESET_MODES, [])
if ha_preset not in presets: if ha_preset not in presets:
msg = "The requested thermostat mode {} is not supported".format(ha_preset) msg = f"The requested thermostat mode {ha_preset} is not supported"
raise AlexaUnsupportedThermostatModeError(msg) raise AlexaUnsupportedThermostatModeError(msg)
service = climate.SERVICE_SET_PRESET_MODE service = climate.SERVICE_SET_PRESET_MODE
@ -754,7 +754,7 @@ async def async_api_set_thermostat_mode(hass, config, directive, context):
operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES) operation_list = entity.attributes.get(climate.ATTR_HVAC_MODES)
ha_mode = next((k for k, v in API_THERMOSTAT_MODES.items() if v == mode), None) ha_mode = next((k for k, v in API_THERMOSTAT_MODES.items() if v == mode), None)
if ha_mode not in operation_list: if ha_mode not in operation_list:
msg = "The requested thermostat mode {} is not supported".format(mode) msg = f"The requested thermostat mode {mode} is not supported"
raise AlexaUnsupportedThermostatModeError(msg) raise AlexaUnsupportedThermostatModeError(msg)
service = climate.SERVICE_SET_HVAC_MODE service = climate.SERVICE_SET_HVAC_MODE

View File

@ -113,7 +113,7 @@ async def async_handle_message(hass, message):
handler = HANDLERS.get(req_type) handler = HANDLERS.get(req_type)
if not handler: if not handler:
raise UnknownRequest("Received unknown request {}".format(req_type)) raise UnknownRequest(f"Received unknown request {req_type}")
return await handler(hass, message) return await handler(hass, message)

View File

@ -60,7 +60,7 @@ async def async_send_changereport_message(
""" """
token = await config.async_get_access_token() token = await config.async_get_access_token()
headers = {"Authorization": "Bearer {}".format(token)} headers = {"Authorization": f"Bearer {token}"}
endpoint = alexa_entity.alexa_id() endpoint = alexa_entity.alexa_id()
@ -125,7 +125,7 @@ async def async_send_add_or_update_message(hass, config, entity_ids):
""" """
token = await config.async_get_access_token() token = await config.async_get_access_token()
headers = {"Authorization": "Bearer {}".format(token)} headers = {"Authorization": f"Bearer {token}"}
endpoints = [] endpoints = []
@ -155,7 +155,7 @@ async def async_send_delete_message(hass, config, entity_ids):
""" """
token = await config.async_get_access_token() token = await config.async_get_access_token()
headers = {"Authorization": "Bearer {}".format(token)} headers = {"Authorization": f"Bearer {token}"}
endpoints = [] endpoints = []

View File

@ -168,7 +168,7 @@ class AlphaVantageForeignExchange(Entity):
if CONF_NAME in config: if CONF_NAME in config:
self._name = config.get(CONF_NAME) self._name = config.get(CONF_NAME)
else: else:
self._name = "{}/{}".format(self._to_currency, self._from_currency) self._name = f"{self._to_currency}/{self._from_currency}"
self._unit_of_measurement = self._to_currency self._unit_of_measurement = self._to_currency
self._icon = ICONS.get(self._from_currency, "USD") self._icon = ICONS.get(self._from_currency, "USD")
self.values = None self.values = None

View File

@ -1,7 +1,22 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_setup": "L'account Ambiclimate \u00e8 configurato." "access_token": "Errore sconosciuto durante la generazione di un token di accesso.",
"already_setup": "L'account Ambiclimate \u00e8 configurato.",
"no_config": "\u00c8 necessario configurare Ambiclimate prima di poter eseguire l'autenticazione con esso. [Leggere le istruzioni] (https://www.home-assistant.io/components/ambiclimate/)."
},
"create_entry": {
"default": "Autenticato con successo con Ambiclimate"
},
"error": {
"follow_link": "Si prega di seguire il link e di autenticarsi prima di premere Invia",
"no_token": "Non autenticato con Ambiclimate"
},
"step": {
"auth": {
"description": "Segui questo [link]({authorization_url}) e <b>Consenti</b> accesso al tuo account Ambiclimate, quindi torna indietro e premi <b>Invia</b> qui sotto. \n (Assicurati che l'URL di richiamata specificato sia {cb_url})",
"title": "Autenticare Ambiclimate"
}
}, },
"title": "Ambiclimate" "title": "Ambiclimate"
} }

View File

@ -3,7 +3,7 @@
"abort": { "abort": {
"access_token": "Nieznany b\u0142\u0105d podczas generowania tokena dost\u0119pu.", "access_token": "Nieznany b\u0142\u0105d podczas generowania tokena dost\u0119pu.",
"already_setup": "Konto Ambiclimate jest skonfigurowane.", "already_setup": "Konto Ambiclimate jest skonfigurowane.",
"no_config": "Musisz skonfigurowa\u0107 Ambiclimate, zanim b\u0119dziesz m\u00f3g\u0142 si\u0119 z nim uwierzytelni\u0107. [Przeczytaj instrukcj\u0119](https://www.home-assistant.io/components/ambiclimate/)." "no_config": "Musisz skonfigurowa\u0107 Ambiclimate, zanim b\u0119dziesz m\u00f3g\u0142 si\u0119 w nim uwierzytelni\u0107. [Przeczytaj instrukcj\u0119](https://www.home-assistant.io/components/ambiclimate/)."
}, },
"create_entry": { "create_entry": {
"default": "Pomy\u015blnie uwierzytelniono z Ambiclimate" "default": "Pomy\u015blnie uwierzytelniono z Ambiclimate"
@ -14,7 +14,7 @@
}, },
"step": { "step": {
"auth": { "auth": {
"description": "Kliknij poni\u017cszy [link]({authorization_url}) i <b>Zezw\u00f3l</b> na dost\u0119p do swojego konta Ambiclimate, a nast\u0119pnie wr\u00f3\u0107 i naci\u015bnij <b>Prze\u015blij</b> poni\u017cej. \n(Upewnij si\u0119, \u017ce podany adres URL to {cb_url})", "description": "Kliknij poni\u017cszy [link]({authorization_url}) i <b>Zezw\u00f3l</b> na dost\u0119p do konta Ambiclimate, a nast\u0119pnie wr\u00f3\u0107 i naci\u015bnij <b>Prze\u015blij</b> poni\u017cej. \n(Upewnij si\u0119, \u017ce podany adres URL to {cb_url})",
"title": "Uwierzytelnienie Ambiclimate" "title": "Uwierzytelnienie Ambiclimate"
} }
}, },

View File

@ -130,7 +130,7 @@ class AmbiclimateFlowHandler(config_entries.ConfigFlow):
return oauth return oauth
def _cb_url(self): def _cb_url(self):
return "{}{}".format(self.hass.config.api.base_url, AUTH_CALLBACK_PATH) return f"{self.hass.config.api.base_url}{AUTH_CALLBACK_PATH}"
async def _get_authorize_url(self): async def _get_authorize_url(self):
oauth = self._generate_oauth() oauth = self._generate_oauth()

View File

@ -4,7 +4,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/components/ambiclimate", "documentation": "https://www.home-assistant.io/components/ambiclimate",
"requirements": [ "requirements": [
"ambiclimate==0.2.0" "ambiclimate==0.2.1"
], ],
"dependencies": [], "dependencies": [],
"codeowners": [ "codeowners": [

View File

@ -13,6 +13,7 @@
}, },
"title": "Inserisci i tuoi dati" "title": "Inserisci i tuoi dati"
} }
} },
"title": "PWS ambientale"
} }
} }

View File

@ -11,7 +11,7 @@
"api_key": "Klucz API", "api_key": "Klucz API",
"app_key": "Klucz aplikacji" "app_key": "Klucz aplikacji"
}, },
"title": "Wprowad\u017a swoje dane" "title": "Wprowad\u017a dane"
} }
}, },
"title": "Ambient PWS" "title": "Ambient PWS"

View File

@ -492,7 +492,7 @@ class AmbientWeatherEntity(Entity):
@property @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
return "{0}_{1}".format(self._station_name, self._sensor_name) return f"{self._station_name}_{self._sensor_name}"
@property @property
def should_poll(self): def should_poll(self):
@ -502,7 +502,7 @@ class AmbientWeatherEntity(Entity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique, unchanging string that represents this sensor.""" """Return a unique, unchanging string that represents this sensor."""
return "{0}_{1}".format(self._mac_address, self._sensor_type) return f"{self._mac_address}_{self._sensor_type}"
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""

View File

@ -490,7 +490,7 @@ class AmcrestCam(Camera):
self._api.go_to_preset(action="start", preset_point_number=preset) self._api.go_to_preset(action="start", preset_point_number=preset)
except AmcrestError as error: except AmcrestError as error:
log_update_error( log_update_error(
_LOGGER, "move", self.name, "camera to preset {}".format(preset), error _LOGGER, "move", self.name, f"camera to preset {preset}", error
) )
def _set_color_bw(self, cbw): def _set_color_bw(self, cbw):
@ -499,7 +499,7 @@ class AmcrestCam(Camera):
self._api.day_night_color = _CBW.index(cbw) self._api.day_night_color = _CBW.index(cbw)
except AmcrestError as error: except AmcrestError as error:
log_update_error( log_update_error(
_LOGGER, "set", self.name, "camera color mode to {}".format(cbw), error _LOGGER, "set", self.name, f"camera color mode to {cbw}", error
) )
else: else:
self._color_bw = cbw self._color_bw = cbw

View File

@ -4,7 +4,7 @@ from .const import DOMAIN
def service_signal(service, ident=None): def service_signal(service, ident=None):
"""Encode service and identifier into signal.""" """Encode service and identifier into signal."""
signal = "{}_{}".format(DOMAIN, service) signal = f"{DOMAIN}_{service}"
if ident: if ident:
signal += "_{}".format(ident.replace(".", "_")) signal += "_{}".format(ident.replace(".", "_"))
return signal return signal

View File

@ -57,7 +57,7 @@ class AmpioSmogQuality(AirQualityEntity):
@property @property
def unique_id(self): def unique_id(self):
"""Return unique_name.""" """Return unique_name."""
return "ampio_smog_{}".format(self._station_id) return f"ampio_smog_{self._station_id}"
@property @property
def particulate_matter_2_5(self): def particulate_matter_2_5(self):

View File

@ -25,7 +25,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
self._sensor = sensor self._sensor = sensor
self._mapped_name = KEY_MAP.get(self._sensor, self._sensor) self._mapped_name = KEY_MAP.get(self._sensor, self._sensor)
self._name = "{} {}".format(name, self._mapped_name) self._name = f"{name} {self._mapped_name}"
self._state = None self._state = None
self._unit = None self._unit = None

View File

@ -39,7 +39,7 @@ class IPWebcamSensor(AndroidIPCamEntity):
self._sensor = sensor self._sensor = sensor
self._mapped_name = KEY_MAP.get(self._sensor, self._sensor) self._mapped_name = KEY_MAP.get(self._sensor, self._sensor)
self._name = "{} {}".format(name, self._mapped_name) self._name = f"{name} {self._mapped_name}"
self._state = None self._state = None
self._unit = None self._unit = None

View File

@ -39,7 +39,7 @@ class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice):
self._setting = setting self._setting = setting
self._mapped_name = KEY_MAP.get(self._setting, self._setting) self._mapped_name = KEY_MAP.get(self._setting, self._setting)
self._name = "{} {}".format(name, self._mapped_name) self._name = f"{name} {self._mapped_name}"
self._state = False self._state = False
@property @property

View File

@ -3,7 +3,7 @@
"name": "Androidtv", "name": "Androidtv",
"documentation": "https://www.home-assistant.io/components/androidtv", "documentation": "https://www.home-assistant.io/components/androidtv",
"requirements": [ "requirements": [
"androidtv==0.0.24" "androidtv==0.0.27"
], ],
"dependencies": [], "dependencies": [],
"codeowners": ["@JeffLIrion"] "codeowners": ["@JeffLIrion"]

View File

@ -392,7 +392,7 @@ class ADBDevice(MediaPlayerDevice):
"""Send an ADB command to an Android TV / Fire TV device.""" """Send an ADB command to an Android TV / Fire TV device."""
key = self._keys.get(cmd) key = self._keys.get(cmd)
if key: if key:
self.aftv.adb_shell("input keyevent {}".format(key)) self.aftv.adb_shell(f"input keyevent {key}")
self._adb_response = None self._adb_response = None
self.schedule_update_ha_state() self.schedule_update_ha_state()
return return
@ -431,7 +431,9 @@ class AndroidTVDevice(ADBDevice):
# Try to connect # Try to connect
self._available = self.aftv.connect(always_log_errors=False) self._available = self.aftv.connect(always_log_errors=False)
# To be safe, wait until the next update to run ADB commands. # To be safe, wait until the next update to run ADB commands if
# using the Python ADB implementation.
if not self.aftv.adb_server_ip:
return return
# If the ADB connection is not intact, don't update. # If the ADB connection is not intact, don't update.
@ -443,7 +445,9 @@ class AndroidTVDevice(ADBDevice):
self.aftv.update() self.aftv.update()
) )
self._state = ANDROIDTV_STATES[state] self._state = ANDROIDTV_STATES.get(state)
if self._state is None:
self._available = False
@property @property
def is_volume_muted(self): def is_volume_muted(self):
@ -506,7 +510,9 @@ class FireTVDevice(ADBDevice):
# Try to connect # Try to connect
self._available = self.aftv.connect(always_log_errors=False) self._available = self.aftv.connect(always_log_errors=False)
# To be safe, wait until the next update to run ADB commands. # To be safe, wait until the next update to run ADB commands if
# using the Python ADB implementation.
if not self.aftv.adb_server_ip:
return return
# If the ADB connection is not intact, don't update. # If the ADB connection is not intact, don't update.
@ -518,7 +524,9 @@ class FireTVDevice(ADBDevice):
self._get_sources self._get_sources
) )
self._state = ANDROIDTV_STATES[state] self._state = ANDROIDTV_STATES.get(state)
if self._state is None:
self._available = False
@property @property
def source(self): def source(self):

View File

@ -81,7 +81,7 @@ class KafkaManager:
self._hass = hass self._hass = hass
self._producer = AIOKafkaProducer( self._producer = AIOKafkaProducer(
loop=hass.loop, loop=hass.loop,
bootstrap_servers="{0}:{1}".format(ip_address, port), bootstrap_servers=f"{ip_address}:{port}",
compression_type="gzip", compression_type="gzip",
) )
self._topic = topic self._topic = topic

View File

@ -138,7 +138,7 @@ class APIEventStream(HomeAssistantView):
if payload is stop_obj: if payload is stop_obj:
break break
msg = "data: {}\n\n".format(payload) msg = f"data: {payload}\n\n"
_LOGGER.debug("STREAM %s WRITING %s", id(stop_obj), msg.strip()) _LOGGER.debug("STREAM %s WRITING %s", id(stop_obj), msg.strip())
await response.write(msg.encode("UTF-8")) await response.write(msg.encode("UTF-8"))
except asyncio.TimeoutError: except asyncio.TimeoutError:
@ -316,7 +316,7 @@ class APIEventView(HomeAssistantView):
event_type, event_data, ha.EventOrigin.remote, self.context(request) event_type, event_data, ha.EventOrigin.remote, self.context(request)
) )
return self.json_message("Event {} fired.".format(event_type)) return self.json_message(f"Event {event_type} fired.")
class APIServicesView(HomeAssistantView): class APIServicesView(HomeAssistantView):
@ -388,7 +388,7 @@ class APITemplateView(HomeAssistantView):
return tpl.async_render(data.get("variables")) return tpl.async_render(data.get("variables"))
except (ValueError, TemplateError) as ex: except (ValueError, TemplateError) as ex:
return self.json_message( return self.json_message(
"Error rendering template: {}".format(ex), HTTP_BAD_REQUEST f"Error rendering template: {ex}", HTTP_BAD_REQUEST
) )

View File

@ -50,7 +50,7 @@ def get_service(hass, config, discovery_info=None):
service = ApnsNotificationService(hass, name, topic, sandbox, cert_file) service = ApnsNotificationService(hass, name, topic, sandbox, cert_file)
hass.services.register( hass.services.register(
DOMAIN, "apns_{}".format(name), service.register, schema=REGISTER_SERVICE_SCHEMA DOMAIN, f"apns_{name}", service.register, schema=REGISTER_SERVICE_SCHEMA
) )
return service return service
@ -98,7 +98,7 @@ class ApnsDevice:
The full id of a device that is tracked by the device The full id of a device that is tracked by the device
tracking component. tracking component.
""" """
return "{}.{}".format(DEVICE_TRACKER_DOMAIN, self.tracking_id) return f"{DEVICE_TRACKER_DOMAIN}.{self.tracking_id}"
@property @property
def disabled(self): def disabled(self):
@ -124,9 +124,9 @@ def _write_device(out, device):
"""Write a single device to file.""" """Write a single device to file."""
attributes = [] attributes = []
if device.name is not None: if device.name is not None:
attributes.append("name: {}".format(device.name)) attributes.append(f"name: {device.name}")
if device.tracking_device_id is not None: if device.tracking_device_id is not None:
attributes.append("tracking_device_id: {}".format(device.tracking_device_id)) attributes.append(f"tracking_device_id: {device.tracking_device_id}")
if device.disabled: if device.disabled:
attributes.append("disabled: True") attributes.append("disabled: True")

View File

@ -3,7 +3,7 @@
"name": "Apple tv", "name": "Apple tv",
"documentation": "https://www.home-assistant.io/components/apple_tv", "documentation": "https://www.home-assistant.io/components/apple_tv",
"requirements": [ "requirements": [
"pyatv==0.3.12" "pyatv==0.3.13"
], ],
"dependencies": ["configurator"], "dependencies": ["configurator"],
"codeowners": [] "codeowners": []

View File

@ -127,6 +127,7 @@ class AppleTvDevice(MediaPlayerDevice):
const.PLAY_STATE_PAUSED, const.PLAY_STATE_PAUSED,
const.PLAY_STATE_FAST_FORWARD, const.PLAY_STATE_FAST_FORWARD,
const.PLAY_STATE_FAST_BACKWARD, const.PLAY_STATE_FAST_BACKWARD,
const.PLAY_STATE_STOPPED,
): ):
# Catch fast forward/backward here so "play" is default action # Catch fast forward/backward here so "play" is default action
return STATE_PAUSED return STATE_PAUSED
@ -212,7 +213,7 @@ class AppleTvDevice(MediaPlayerDevice):
title = self._playing.title title = self._playing.title
return title if title else "No title" return title if title else "No title"
return "Establishing a connection to {0}...".format(self._name) return f"Establishing a connection to {self._name}..."
@property @property
def supported_features(self): def supported_features(self):

View File

@ -70,7 +70,7 @@ def gps_accuracy(gps, posambiguity: int) -> int:
accuracy = round(dist_m) accuracy = round(dist_m)
else: else:
message = "APRS position ambiguity must be 0-4, not '{0}'.".format(posambiguity) message = f"APRS position ambiguity must be 0-4, not '{posambiguity}'."
raise ValueError(message) raise ValueError(message)
return accuracy return accuracy
@ -147,8 +147,7 @@ class AprsListenerThread(threading.Thread):
) )
self.ais.connect() self.ais.connect()
self.start_complete( self.start_complete(
True, True, f"Connected to {self.host} with callsign {self.callsign}."
"Connected to {0} with callsign {1}.".format(self.host, self.callsign),
) )
self.ais.consumer(callback=self.rx_msg, immortal=True) self.ais.consumer(callback=self.rx_msg, immortal=True)
except (AprsConnectionError, LoginError) as err: except (AprsConnectionError, LoginError) as err:

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -0,0 +1,5 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -9,5 +9,5 @@ DEFAULT_PORT = 50000
DEFAULT_NAME = "Arcam FMJ" DEFAULT_NAME = "Arcam FMJ"
DEFAULT_SCAN_INTERVAL = 5 DEFAULT_SCAN_INTERVAL = 5
DOMAIN_DATA_ENTRIES = "{}.entries".format(DOMAIN) DOMAIN_DATA_ENTRIES = f"{DOMAIN}.entries"
DOMAIN_DATA_CONFIG = "{}.config".format(DOMAIN) DOMAIN_DATA_CONFIG = f"{DOMAIN}.config"

View File

@ -319,7 +319,7 @@ class ArcamFmj(MediaPlayerDevice):
channel = self.media_channel channel = self.media_channel
if channel: if channel:
value = "{} - {}".format(source.name, channel) value = f"{source.name} - {channel}"
else: else:
value = source.name value = source.name
return value return value

View File

@ -73,9 +73,7 @@ class ArestBinarySensor(BinarySensorDevice):
self._pin = pin self._pin = pin
if self._pin is not None: if self._pin is not None:
request = requests.get( request = requests.get(f"{self._resource}/mode/{self._pin}/i", timeout=10)
"{}/mode/{}/i".format(self._resource, self._pin), timeout=10
)
if request.status_code != 200: if request.status_code != 200:
_LOGGER.error("Can't set mode of %s", self._resource) _LOGGER.error("Can't set mode of %s", self._resource)
@ -112,9 +110,7 @@ class ArestData:
def update(self): def update(self):
"""Get the latest data from aREST device.""" """Get the latest data from aREST device."""
try: try:
response = requests.get( response = requests.get(f"{self._resource}/digital/{self._pin}", timeout=10)
"{}/digital/{}".format(self._resource, self._pin), timeout=10
)
self.data = {"state": response.json()["return_value"]} self.data = {"state": response.json()["return_value"]}
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device '%s'", self._resource) _LOGGER.error("No route to device '%s'", self._resource)

View File

@ -148,9 +148,7 @@ class ArestSensor(Entity):
self._renderer = renderer self._renderer = renderer
if self._pin is not None: if self._pin is not None:
request = requests.get( request = requests.get(f"{self._resource}/mode/{self._pin}/i", timeout=10)
"{}/mode/{}/i".format(self._resource, self._pin), timeout=10
)
if request.status_code != 200: if request.status_code != 200:
_LOGGER.error("Can't set mode of %s", self._resource) _LOGGER.error("Can't set mode of %s", self._resource)
@ -212,7 +210,7 @@ class ArestData:
self.data = {"value": response.json()["return_value"]} self.data = {"value": response.json()["return_value"]}
except TypeError: except TypeError:
response = requests.get( response = requests.get(
"{}/digital/{}".format(self._resource, self._pin), timeout=10 f"{self._resource}/digital/{self._pin}", timeout=10
) )
self.data = {"value": response.json()["return_value"]} self.data = {"value": response.json()["return_value"]}
self.available = True self.available = True

View File

@ -114,7 +114,7 @@ class ArestSwitchFunction(ArestSwitchBase):
super().__init__(resource, location, name) super().__init__(resource, location, name)
self._func = func self._func = func
request = requests.get("{}/{}".format(self._resource, self._func), timeout=10) request = requests.get(f"{self._resource}/{self._func}", timeout=10)
if request.status_code != 200: if request.status_code != 200:
_LOGGER.error("Can't find function") _LOGGER.error("Can't find function")
@ -130,9 +130,7 @@ class ArestSwitchFunction(ArestSwitchBase):
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the device on.""" """Turn the device on."""
request = requests.get( request = requests.get(
"{}/{}".format(self._resource, self._func), f"{self._resource}/{self._func}", timeout=10, params={"params": "1"}
timeout=10,
params={"params": "1"},
) )
if request.status_code == 200: if request.status_code == 200:
@ -143,9 +141,7 @@ class ArestSwitchFunction(ArestSwitchBase):
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the device off.""" """Turn the device off."""
request = requests.get( request = requests.get(
"{}/{}".format(self._resource, self._func), f"{self._resource}/{self._func}", timeout=10, params={"params": "0"}
timeout=10,
params={"params": "0"},
) )
if request.status_code == 200: if request.status_code == 200:
@ -158,9 +154,7 @@ class ArestSwitchFunction(ArestSwitchBase):
def update(self): def update(self):
"""Get the latest data from aREST API and update the state.""" """Get the latest data from aREST API and update the state."""
try: try:
request = requests.get( request = requests.get(f"{self._resource}/{self._func}", timeout=10)
"{}/{}".format(self._resource, self._func), timeout=10
)
self._state = request.json()["return_value"] != 0 self._state = request.json()["return_value"] != 0
self._available = True self._available = True
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
@ -177,9 +171,7 @@ class ArestSwitchPin(ArestSwitchBase):
self._pin = pin self._pin = pin
self.invert = invert self.invert = invert
request = requests.get( request = requests.get(f"{self._resource}/mode/{self._pin}/o", timeout=10)
"{}/mode/{}/o".format(self._resource, self._pin), timeout=10
)
if request.status_code != 200: if request.status_code != 200:
_LOGGER.error("Can't set mode") _LOGGER.error("Can't set mode")
self._available = False self._available = False
@ -188,8 +180,7 @@ class ArestSwitchPin(ArestSwitchBase):
"""Turn the device on.""" """Turn the device on."""
turn_on_payload = int(not self.invert) turn_on_payload = int(not self.invert)
request = requests.get( request = requests.get(
"{}/digital/{}/{}".format(self._resource, self._pin, turn_on_payload), f"{self._resource}/digital/{self._pin}/{turn_on_payload}", timeout=10
timeout=10,
) )
if request.status_code == 200: if request.status_code == 200:
self._state = True self._state = True
@ -200,8 +191,7 @@ class ArestSwitchPin(ArestSwitchBase):
"""Turn the device off.""" """Turn the device off."""
turn_off_payload = int(self.invert) turn_off_payload = int(self.invert)
request = requests.get( request = requests.get(
"{}/digital/{}/{}".format(self._resource, self._pin, turn_off_payload), f"{self._resource}/digital/{self._pin}/{turn_off_payload}", timeout=10
timeout=10,
) )
if request.status_code == 200: if request.status_code == 200:
self._state = False self._state = False
@ -211,9 +201,7 @@ class ArestSwitchPin(ArestSwitchBase):
def update(self): def update(self):
"""Get the latest data from aREST API and update the state.""" """Get the latest data from aREST API and update the state."""
try: try:
request = requests.get( request = requests.get(f"{self._resource}/digital/{self._pin}", timeout=10)
"{}/digital/{}".format(self._resource, self._pin), timeout=10
)
status_value = int(self.invert) status_value = int(self.invert)
self._state = request.json()["return_value"] != status_value self._state = request.json()["return_value"] != status_value
self._available = True self._available = True

View File

@ -0,0 +1 @@
"""Support for Atome devices connected to a Linky Energy Meter."""

View File

@ -0,0 +1,8 @@
{
"domain": "atome",
"name": "Atome",
"documentation": "https://www.home-assistant.io/components/atome",
"dependencies": [],
"codeowners": ["@baqs"],
"requirements": ["pyatome==0.1.1"]
}

View File

@ -0,0 +1,279 @@
"""Linky Atome."""
import logging
from datetime import timedelta
import voluptuous as vol
from pyatome.client import AtomeClient
from pyatome.client import PyAtomeError
from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
CONF_NAME,
DEVICE_CLASS_POWER,
POWER_WATT,
ENERGY_KILO_WATT_HOUR,
)
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "atome"
LIVE_SCAN_INTERVAL = timedelta(seconds=30)
DAILY_SCAN_INTERVAL = timedelta(seconds=150)
WEEKLY_SCAN_INTERVAL = timedelta(hours=1)
MONTHLY_SCAN_INTERVAL = timedelta(hours=1)
YEARLY_SCAN_INTERVAL = timedelta(days=1)
LIVE_NAME = "Atome Live Power"
DAILY_NAME = "Atome Daily"
WEEKLY_NAME = "Atome Weekly"
MONTHLY_NAME = "Atome Monthly"
YEARLY_NAME = "Atome Yearly"
LIVE_TYPE = "live"
DAILY_TYPE = "day"
WEEKLY_TYPE = "week"
MONTHLY_TYPE = "month"
YEARLY_TYPE = "year"
ICON = "mdi:flash"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Atome sensor."""
username = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
try:
atome_client = AtomeClient(username, password)
atome_client.login()
except PyAtomeError as exp:
_LOGGER.error(exp)
return
data = AtomeData(atome_client)
sensors = []
sensors.append(AtomeSensor(data, LIVE_NAME, LIVE_TYPE))
sensors.append(AtomeSensor(data, DAILY_NAME, DAILY_TYPE))
sensors.append(AtomeSensor(data, WEEKLY_NAME, WEEKLY_TYPE))
sensors.append(AtomeSensor(data, MONTHLY_NAME, MONTHLY_TYPE))
sensors.append(AtomeSensor(data, YEARLY_NAME, YEARLY_TYPE))
add_entities(sensors, True)
class AtomeData:
"""Stores data retrieved from Neurio sensor."""
def __init__(self, client: AtomeClient):
"""Initialize the data."""
self.atome_client = client
self._live_power = None
self._subscribed_power = None
self._is_connected = None
self._day_usage = None
self._day_price = None
self._week_usage = None
self._week_price = None
self._month_usage = None
self._month_price = None
self._year_usage = None
self._year_price = None
@property
def live_power(self):
"""Return latest active power value."""
return self._live_power
@property
def subscribed_power(self):
"""Return latest active power value."""
return self._subscribed_power
@property
def is_connected(self):
"""Return latest active power value."""
return self._is_connected
@Throttle(LIVE_SCAN_INTERVAL)
def update_live_usage(self):
"""Return current power value."""
try:
values = self.atome_client.get_live()
self._live_power = values["last"]
self._subscribed_power = values["subscribed"]
self._is_connected = values["isConnected"]
_LOGGER.debug(
"Updating Atome live data. Got: %d, isConnected: %s, subscribed: %d",
self._live_power,
self._is_connected,
self._subscribed_power,
)
except KeyError as error:
_LOGGER.error("Missing last value in values: %s: %s", values, error)
@property
def day_usage(self):
"""Return latest daily usage value."""
return self._day_usage
@property
def day_price(self):
"""Return latest daily usage value."""
return self._day_price
@Throttle(DAILY_SCAN_INTERVAL)
def update_day_usage(self):
"""Return current daily power usage."""
try:
values = self.atome_client.get_consumption(DAILY_TYPE)
self._day_usage = values["total"] / 1000
self._day_price = values["price"]
_LOGGER.debug("Updating Atome daily data. Got: %d.", self._day_usage)
except KeyError as error:
_LOGGER.error("Missing last value in values: %s: %s", values, error)
@property
def week_usage(self):
"""Return latest weekly usage value."""
return self._week_usage
@property
def week_price(self):
"""Return latest weekly usage value."""
return self._week_price
@Throttle(WEEKLY_SCAN_INTERVAL)
def update_week_usage(self):
"""Return current weekly power usage."""
try:
values = self.atome_client.get_consumption(WEEKLY_TYPE)
self._week_usage = values["total"] / 1000
self._week_price = values["price"]
_LOGGER.debug("Updating Atome weekly data. Got: %d.", self._week_usage)
except KeyError as error:
_LOGGER.error("Missing last value in values: %s: %s", values, error)
@property
def month_usage(self):
"""Return latest monthly usage value."""
return self._month_usage
@property
def month_price(self):
"""Return latest monthly usage value."""
return self._month_price
@Throttle(MONTHLY_SCAN_INTERVAL)
def update_month_usage(self):
"""Return current monthly power usage."""
try:
values = self.atome_client.get_consumption(MONTHLY_TYPE)
self._month_usage = values["total"] / 1000
self._month_price = values["price"]
_LOGGER.debug("Updating Atome monthly data. Got: %d.", self._month_usage)
except KeyError as error:
_LOGGER.error("Missing last value in values: %s: %s", values, error)
@property
def year_usage(self):
"""Return latest yearly usage value."""
return self._year_usage
@property
def year_price(self):
"""Return latest yearly usage value."""
return self._year_price
@Throttle(YEARLY_SCAN_INTERVAL)
def update_year_usage(self):
"""Return current yearly power usage."""
try:
values = self.atome_client.get_consumption(YEARLY_TYPE)
self._year_usage = values["total"] / 1000
self._year_price = values["price"]
_LOGGER.debug("Updating Atome yearly data. Got: %d.", self._year_usage)
except KeyError as error:
_LOGGER.error("Missing last value in values: %s: %s", values, error)
class AtomeSensor(Entity):
"""Representation of a sensor entity for Atome."""
def __init__(self, data, name, sensor_type):
"""Initialize the sensor."""
self._name = name
self._data = data
self._state = None
self._attributes = {}
self._sensor_type = sensor_type
if sensor_type == LIVE_TYPE:
self._unit_of_measurement = POWER_WATT
else:
self._unit_of_measurement = ENERGY_KILO_WATT_HOUR
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._attributes
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return ICON
@property
def device_class(self):
"""Return the device class."""
return DEVICE_CLASS_POWER
def update(self):
"""Update device state."""
update_function = getattr(self._data, f"update_{self._sensor_type}_usage")
update_function()
if self._sensor_type == LIVE_TYPE:
self._state = self._data.live_power
self._attributes["subscribed_power"] = self._data.subscribed_power
self._attributes["is_connected"] = self._data.is_connected
else:
self._state = getattr(self._data, f"{self._sensor_type}_usage")
self._attributes["price"] = getattr(
self._data, f"{self._sensor_type}_price"
)

View File

@ -73,4 +73,4 @@ class AugustCamera(Camera):
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Get the unique id of the camera.""" """Get the unique id of the camera."""
return "{:s}_camera".format(self._doorbell.device_id) return f"{self._doorbell.device_id:s}_camera"

View File

@ -93,4 +93,4 @@ class AugustLock(LockDevice):
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
"""Get the unique id of the lock.""" """Get the unique id of the lock."""
return "{:s}_lock".format(self._lock.device_id) return f"{self._lock.device_id:s}_lock"

View File

@ -64,7 +64,7 @@ class AuroraSensor(BinarySensorDevice):
@property @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
return "{}".format(self._name) return f"{self._name}"
@property @property
def is_on(self): def is_on(self):

View File

@ -49,7 +49,7 @@ class AuroraABBSolarPVMonitorSensor(Entity):
def __init__(self, client, name, typename): def __init__(self, client, name, typename):
"""Initialize the sensor.""" """Initialize the sensor."""
self._name = "{} {}".format(name, typename) self._name = f"{name} {typename}"
self.client = client self.client = client
self._state = None self._state = None

View File

@ -10,7 +10,7 @@
"step": { "step": {
"init": { "init": {
"description": "Selezionare uno dei servizi di notifica:", "description": "Selezionare uno dei servizi di notifica:",
"title": "Imposta la password one-time fornita dal componente di notifica" "title": "Imposta la password monouso fornita dal componente di notifica"
}, },
"setup": { "setup": {
"description": "\u00c8 stata inviata una password monouso tramite **notify.{notify_service}**. Per favore, inseriscila qui sotto:", "description": "\u00c8 stata inviata una password monouso tramite **notify.{notify_service}**. Per favore, inseriscila qui sotto:",
@ -25,7 +25,7 @@
}, },
"step": { "step": {
"init": { "init": {
"description": "Per attivare l'autenticazione a due fattori utilizzando password monouso basate sul tempo, eseguire la scansione del codice QR con l'app di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \n Dopo aver scansionato il codice, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con codice ** ` {code} ` **.", "description": "Per attivare l'autenticazione a due fattori utilizzando le password monouso basate sul tempo, eseguire la scansione del codice QR con l'app di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \nDopo la scansione, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con il codice ** ` {code} ` **.",
"title": "Imposta l'autenticazione a due fattori usando TOTP" "title": "Imposta l'autenticazione a due fattori usando TOTP"
} }
}, },

View File

@ -25,7 +25,7 @@
}, },
"step": { "step": {
"init": { "init": {
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.", "description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [\uad6c\uae00 OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
"title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131" "title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
} }
}, },

View File

@ -13,7 +13,7 @@
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144" "title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
}, },
"setup": { "setup": {
"description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez **notify.{notify_service}**. Wpisz je poni\u017cej:", "description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez **notify.{notify_service}**. Wprowad\u017a je poni\u017cej:",
"title": "Sprawd\u017a konfiguracj\u0119" "title": "Sprawd\u017a konfiguracj\u0119"
} }
}, },

View File

@ -34,7 +34,7 @@ async def async_setup(hass):
"""Create a setup flow. handler is a mfa module.""" """Create a setup flow. handler is a mfa module."""
mfa_module = hass.auth.get_auth_mfa_module(handler) mfa_module = hass.auth.get_auth_mfa_module(handler)
if mfa_module is None: if mfa_module is None:
raise ValueError("Mfa module {} is not found".format(handler)) raise ValueError(f"Mfa module {handler} is not found")
user_id = data.pop("user_id") user_id = data.pop("user_id")
return await mfa_module.async_setup_flow(user_id) return await mfa_module.async_setup_flow(user_id)
@ -80,9 +80,7 @@ def websocket_setup_mfa(
if mfa_module is None: if mfa_module is None:
connection.send_message( connection.send_message(
websocket_api.error_message( websocket_api.error_message(
msg["id"], msg["id"], "no_module", f"MFA module {mfa_module_id} is not found"
"no_module",
"MFA module {} is not found".format(mfa_module_id),
) )
) )
return return
@ -117,7 +115,7 @@ def websocket_depose_mfa(
websocket_api.error_message( websocket_api.error_message(
msg["id"], msg["id"],
"disable_failed", "disable_failed",
"Cannot disable MFA Module {}: {}".format(mfa_module_id, err), f"Cannot disable MFA Module {mfa_module_id}: {err}",
) )
) )
return return

View File

@ -6,6 +6,9 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_NAME, ATTR_NAME,
@ -143,7 +146,7 @@ async def async_setup(hass, config):
async def turn_onoff_service_handler(service_call): async def turn_onoff_service_handler(service_call):
"""Handle automation turn on/off service calls.""" """Handle automation turn on/off service calls."""
tasks = [] tasks = []
method = "async_{}".format(service_call.service) method = f"async_{service_call.service}"
for entity in await component.async_extract_from_service(service_call): for entity in await component.async_extract_from_service(service_call):
tasks.append(getattr(entity, method)()) tasks.append(getattr(entity, method)())
@ -378,7 +381,7 @@ async def _async_process_config(hass, config, component):
for list_no, config_block in enumerate(conf): for list_no, config_block in enumerate(conf):
automation_id = config_block.get(CONF_ID) automation_id = config_block.get(CONF_ID)
name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key, list_no) name = config_block.get(CONF_ALIAS) or f"{config_key} {list_no}"
hidden = config_block[CONF_HIDE_ENTITY] hidden = config_block[CONF_HIDE_ENTITY]
initial_state = config_block.get(CONF_INITIAL_STATE) initial_state = config_block.get(CONF_INITIAL_STATE)
@ -386,7 +389,7 @@ async def _async_process_config(hass, config, component):
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name) action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name)
if CONF_CONDITION in config_block: if CONF_CONDITION in config_block:
cond_func = _async_process_if(hass, config, config_block) cond_func = await _async_process_if(hass, config, config_block)
if cond_func is None: if cond_func is None:
continue continue
@ -431,20 +434,20 @@ def _async_get_action(hass, config, name):
await script_obj.async_run(variables, context) await script_obj.async_run(variables, context)
except Exception as err: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
script_obj.async_log_exception( script_obj.async_log_exception(
_LOGGER, "Error while executing automation {}".format(entity_id), err _LOGGER, f"Error while executing automation {entity_id}", err
) )
return action return action
def _async_process_if(hass, config, p_config): async def _async_process_if(hass, config, p_config):
"""Process if checks.""" """Process if checks."""
if_configs = p_config.get(CONF_CONDITION) if_configs = p_config.get(CONF_CONDITION)
checks = [] checks = []
for if_config in if_configs: for if_config in if_configs:
try: try:
checks.append(condition.async_from_config(if_config, False)) checks.append(await condition.async_from_config(hass, if_config, False))
except HomeAssistantError as ex: except HomeAssistantError as ex:
_LOGGER.warning("Invalid condition: %s", ex) _LOGGER.warning("Invalid condition: %s", ex)
return None return None
@ -467,7 +470,10 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action):
for conf in trigger_configs: for conf in trigger_configs:
platform = importlib.import_module(".{}".format(conf[CONF_PLATFORM]), __name__) platform = importlib.import_module(".{}".format(conf[CONF_PLATFORM]), __name__)
try:
remove = await platform.async_trigger(hass, conf, action, info) remove = await platform.async_trigger(hass, conf, action, info)
except InvalidDeviceAutomationConfig:
remove = False
if not remove: if not remove:
_LOGGER.error("Error setting up trigger %s", name) _LOGGER.error("Error setting up trigger %s", name)

View File

@ -4,6 +4,7 @@
"documentation": "https://www.home-assistant.io/components/automation", "documentation": "https://www.home-assistant.io/components/automation",
"requirements": [], "requirements": [],
"dependencies": [ "dependencies": [
"device_automation",
"group", "group",
"webhook" "webhook"
], ],

View File

@ -150,7 +150,7 @@ class AwairSensor(Entity):
"""Initialize the sensor.""" """Initialize the sensor."""
self._uuid = device[CONF_UUID] self._uuid = device[CONF_UUID]
self._device_class = SENSOR_TYPES[sensor_type]["device_class"] self._device_class = SENSOR_TYPES[sensor_type]["device_class"]
self._name = "Awair {}".format(self._device_class) self._name = f"Awair {self._device_class}"
unit = SENSOR_TYPES[sensor_type]["unit_of_measurement"] unit = SENSOR_TYPES[sensor_type]["unit_of_measurement"]
self._unit_of_measurement = unit self._unit_of_measurement = unit
self._data = data self._data = data
@ -202,7 +202,7 @@ class AwairSensor(Entity):
@property @property
def unique_id(self): def unique_id(self):
"""Return the unique id of this entity.""" """Return the unique id of this entity."""
return "{}_{}".format(self._uuid, self._type) return f"{self._uuid}_{self._type}"
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):

View File

@ -3,6 +3,7 @@
"abort": { "abort": {
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato",
"bad_config_file": "Dati errati dal file di configurazione", "bad_config_file": "Dati errati dal file di configurazione",
"link_local_address": "Gli indirizzi locali di collegamento non sono supportati",
"not_axis_device": "Il dispositivo rilevato non \u00e8 un dispositivo Axis" "not_axis_device": "Il dispositivo rilevato non \u00e8 un dispositivo Axis"
}, },
"error": { "error": {

View File

@ -72,7 +72,7 @@ class AxisEventBase(AxisEntityBase):
@property @property
def name(self): def name(self):
"""Return the name of the event.""" """Return the name of the event."""
return "{} {} {}".format(self.device.name, self.event.TYPE, self.event.id) return f"{self.device.name} {self.event.TYPE} {self.event.id}"
@property @property
def should_poll(self): def should_poll(self):
@ -82,4 +82,4 @@ class AxisEventBase(AxisEntityBase):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique identifier for this device.""" """Return a unique identifier for this device."""
return "{}-{}-{}".format(self.device.serial, self.event.topic, self.event.id) return f"{self.device.serial}-{self.event.topic}-{self.event.id}"

View File

@ -92,4 +92,4 @@ class AxisCamera(AxisEntityBase, MjpegCamera):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique identifier for this device.""" """Return a unique identifier for this device."""
return "{}-camera".format(self.device.serial) return f"{self.device.serial}-camera"

View File

@ -56,8 +56,7 @@ def configured_devices(hass):
} }
@config_entries.HANDLERS.register(DOMAIN) class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
class AxisFlowHandler(config_entries.ConfigFlow):
"""Handle a Axis config flow.""" """Handle a Axis config flow."""
VERSION = 1 VERSION = 1
@ -138,9 +137,9 @@ class AxisFlowHandler(config_entries.ConfigFlow):
if entry.data[CONF_MODEL] == self.model if entry.data[CONF_MODEL] == self.model
] ]
self.name = "{}".format(self.model) self.name = f"{self.model}"
for idx in range(len(same_model) + 1): for idx in range(len(same_model) + 1):
self.name = "{} {}".format(self.model, idx) self.name = f"{self.model} {idx}"
if self.name not in same_model: if self.name not in same_model:
break break
@ -151,7 +150,7 @@ class AxisFlowHandler(config_entries.ConfigFlow):
CONF_MODEL: self.model, CONF_MODEL: self.model,
} }
title = "{} - {}".format(self.model, self.serial_number) title = f"{self.model} - {self.serial_number}"
return self.async_create_entry(title=title, data=data) return self.async_create_entry(title=title, data=data)
async def _update_entry(self, entry, host): async def _update_entry(self, entry, host):

View File

@ -65,7 +65,7 @@ class AxisNetworkDevice:
connections={(CONNECTION_NETWORK_MAC, self.serial)}, connections={(CONNECTION_NETWORK_MAC, self.serial)},
identifiers={(DOMAIN, self.serial)}, identifiers={(DOMAIN, self.serial)},
manufacturer="Axis Communications AB", manufacturer="Axis Communications AB",
model="{} {}".format(self.model, self.product_type), model=f"{self.model} {self.product_type}",
name=self.name, name=self.name,
sw_version=self.fw_version, sw_version=self.fw_version,
) )
@ -115,7 +115,7 @@ class AxisNetworkDevice:
@property @property
def event_new_address(self): def event_new_address(self):
"""Device specific event to signal new device address.""" """Device specific event to signal new device address."""
return "axis_new_address_{}".format(self.serial) return f"axis_new_address_{self.serial}"
@staticmethod @staticmethod
async def async_new_address_callback(hass, entry): async def async_new_address_callback(hass, entry):
@ -131,7 +131,7 @@ class AxisNetworkDevice:
@property @property
def event_reachable(self): def event_reachable(self):
"""Device specific event to signal a change in connection status.""" """Device specific event to signal a change in connection status."""
return "axis_reachable_{}".format(self.serial) return f"axis_reachable_{self.serial}"
@callback @callback
def async_connection_status_callback(self, status): def async_connection_status_callback(self, status):
@ -149,7 +149,7 @@ class AxisNetworkDevice:
@property @property
def event_new_sensor(self): def event_new_sensor(self):
"""Device specific event to signal new sensor available.""" """Device specific event to signal new sensor available."""
return "axis_add_sensor_{}".format(self.serial) return f"axis_add_sensor_{self.serial}"
@callback @callback
def async_event_callback(self, action, event_id): def async_event_callback(self, action, event_id):

View File

@ -2,6 +2,7 @@
from collections import namedtuple from collections import namedtuple
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import List
import voluptuous as vol import voluptuous as vol
@ -41,12 +42,11 @@ class BboxDeviceScanner(DeviceScanner):
def __init__(self, config): def __init__(self, config):
"""Get host from config.""" """Get host from config."""
from typing import List # noqa: pylint: disable=unused-import
self.host = config[CONF_HOST] self.host = config[CONF_HOST]
"""Initialize the scanner.""" """Initialize the scanner."""
self.last_results = [] # type: List[Device] self.last_results: List[Device] = []
self.success_init = self._update_info() self.success_init = self._update_info()
_LOGGER.info("Scanner initialized") _LOGGER.info("Scanner initialized")

View File

@ -13,7 +13,7 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
BANDWIDTH_MEGABITS_SECONDS = "Mb/s" # type: str BANDWIDTH_MEGABITS_SECONDS = "Mb/s"
ATTRIBUTION = "Powered by Bouygues Telecom" ATTRIBUTION = "Powered by Bouygues Telecom"
@ -91,7 +91,7 @@ class BboxSensor(Entity):
@property @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
return "{} {}".format(self.client_name, self._name) return f"{self.client_name} {self._name}"
@property @property
def state(self): def state(self):

View File

@ -0,0 +1 @@
"""The beewi_smartclim component."""

View File

@ -0,0 +1,12 @@
{
"domain": "beewi_smartclim",
"name": "BeeWi SmartClim BLE sensor",
"documentation": "https://www.home-assistant.io/components/beewi_smartclim",
"requirements": [
"beewi_smartclim==0.0.7"
],
"dependencies": [],
"codeowners": [
"@alemuro"
]
}

View File

@ -0,0 +1,108 @@
"""Platform for beewi_smartclim integration."""
import logging
from beewi_smartclim import BeewiSmartClimPoller
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_NAME,
CONF_MAC,
TEMP_CELSIUS,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_BATTERY,
)
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
# Default values
DEFAULT_NAME = "BeeWi SmartClim"
# Sensor config
SENSOR_TYPES = [
[DEVICE_CLASS_TEMPERATURE, "Temperature", TEMP_CELSIUS],
[DEVICE_CLASS_HUMIDITY, "Humidity", "%"],
[DEVICE_CLASS_BATTERY, "Battery", "%"],
]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_MAC): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the beewi_smartclim platform."""
mac = config[CONF_MAC]
prefix = config[CONF_NAME]
poller = BeewiSmartClimPoller(mac)
sensors = []
for sensor_type in SENSOR_TYPES:
device = sensor_type[0]
name = sensor_type[1]
unit = sensor_type[2]
# `prefix` is the name configured by the user for the sensor, we're appending
# the device type at the end of the name (garden -> garden temperature)
if prefix:
name = f"{prefix} {name}"
sensors.append(BeewiSmartclimSensor(poller, name, mac, device, unit))
add_entities(sensors)
class BeewiSmartclimSensor(Entity):
"""Representation of a Sensor."""
def __init__(self, poller, name, mac, device, unit):
"""Initialize the sensor."""
self._poller = poller
self._name = name
self._mac = mac
self._device = device
self._unit = unit
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor. State is returned in Celsius."""
return self._state
@property
def device_class(self):
"""Device class of this entity."""
return self._device
@property
def unique_id(self):
"""Return a unique, HASS-friendly identifier for this entity."""
return f"{self._mac}_{self._device}"
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit
def update(self):
"""Fetch new state data from the poller."""
self._poller.update_sensor()
self._state = None
if self._device == DEVICE_CLASS_TEMPERATURE:
self._state = self._poller.get_temperature()
if self._device == DEVICE_CLASS_HUMIDITY:
self._state = self._poller.get_humidity()
if self._device == DEVICE_CLASS_BATTERY:
self._state = self._poller.get_battery()

0
homeassistant/components/bizkaibus/sensor.py Executable file → Normal file
View File

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