Automatically generate config flow list (#23802)

* Add config flow to manifest.json

* Still load config flows via config flow platform

* Fix typo

* Lint

* Update config_flows.py"

* Catch import error when setting up entry

* Lint

* Fix tests

* Fix imports

* Lint

* Fix Unifi tests

* Fix translation test

* Add homekit_controller config flow
pull/23845/head
Paulus Schoutsen 2019-05-13 01:16:55 -07:00 committed by GitHub
parent b8cbd39985
commit 1e22c8daca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
106 changed files with 742 additions and 440 deletions

View File

@ -1,6 +1,7 @@
{ {
"domain": "ambiclimate", "domain": "ambiclimate",
"name": "Ambiclimate", "name": "Ambiclimate",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/ambiclimate", "documentation": "https://www.home-assistant.io/components/ambiclimate",
"requirements": [ "requirements": [
"ambiclimate==0.1.1" "ambiclimate==0.1.1"

View File

@ -1,6 +1,7 @@
{ {
"domain": "ambient_station", "domain": "ambient_station",
"name": "Ambient station", "name": "Ambient station",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/ambient_station", "documentation": "https://www.home-assistant.io/components/ambient_station",
"requirements": [ "requirements": [
"aioambient==0.3.0" "aioambient==0.3.0"

View File

@ -1,6 +1,7 @@
{ {
"domain": "axis", "domain": "axis",
"name": "Axis", "name": "Axis",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/axis", "documentation": "https://www.home-assistant.io/components/axis",
"requirements": ["axis==22"], "requirements": ["axis==22"],
"dependencies": [], "dependencies": [],

View File

@ -1,8 +1,7 @@
"""Component to embed Google Cast.""" """Component to embed Google Cast."""
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.helpers import config_entry_flow
DOMAIN = 'cast' from .const import DOMAIN
async def async_setup(hass, config): async def async_setup(hass, config):
@ -23,15 +22,3 @@ async def async_setup_entry(hass, entry):
hass.async_create_task(hass.config_entries.async_forward_entry_setup( hass.async_create_task(hass.config_entries.async_forward_entry_setup(
entry, 'media_player')) entry, 'media_player'))
return True return True
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
from pychromecast.discovery import discover_chromecasts
return await hass.async_add_executor_job(discover_chromecasts)
config_entry_flow.register_discovery_flow(
DOMAIN, 'Google Cast', _async_has_devices,
config_entries.CONN_CLASS_LOCAL_PUSH)

View File

@ -0,0 +1,16 @@
"""Config flow for Cast."""
from homeassistant.helpers import config_entry_flow
from homeassistant import config_entries
from .const import DOMAIN
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
from pychromecast.discovery import discover_chromecasts
return await hass.async_add_executor_job(discover_chromecasts)
config_entry_flow.register_discovery_flow(
DOMAIN, 'Google Cast', _async_has_devices,
config_entries.CONN_CLASS_LOCAL_PUSH)

View File

@ -0,0 +1,3 @@
"""Consts for Cast integration."""
DOMAIN = 'cast'

View File

@ -1,6 +1,7 @@
{ {
"domain": "cast", "domain": "cast",
"name": "Cast", "name": "Cast",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/cast", "documentation": "https://www.home-assistant.io/components/cast",
"requirements": [ "requirements": [
"pychromecast==3.2.1" "pychromecast==3.2.1"

View File

@ -6,6 +6,7 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.exceptions import Unauthorized from homeassistant.exceptions import Unauthorized
from homeassistant.helpers.data_entry_flow import ( from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerResourceView) FlowManagerIndexView, FlowManagerResourceView)
from homeassistant.generated import config_flows
async def async_setup(hass): async def async_setup(hass):
@ -172,7 +173,7 @@ class ConfigManagerAvailableFlowView(HomeAssistantView):
async def get(self, request): async def get(self, request):
"""List available flow handlers.""" """List available flow handlers."""
return self.json(config_entries.FLOWS) return self.json(config_flows.FLOWS)
class OptionManagerFlowIndexView(FlowManagerIndexView): class OptionManagerFlowIndexView(FlowManagerIndexView):

View File

@ -1,6 +1,7 @@
{ {
"domain": "daikin", "domain": "daikin",
"name": "Daikin", "name": "Daikin",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/daikin", "documentation": "https://www.home-assistant.io/components/daikin",
"requirements": [ "requirements": [
"pydaikin==1.4.0" "pydaikin==1.4.0"

View File

@ -1,6 +1,7 @@
{ {
"domain": "deconz", "domain": "deconz",
"name": "Deconz", "name": "Deconz",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/deconz", "documentation": "https://www.home-assistant.io/components/deconz",
"requirements": [ "requirements": [
"pydeconz==58" "pydeconz==58"

View File

@ -8,9 +8,10 @@ from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent, template, config_entry_flow from homeassistant.helpers import intent, template, config_entry_flow
_LOGGER = logging.getLogger(__name__) from .const import DOMAIN
DOMAIN = 'dialogflow'
_LOGGER = logging.getLogger(__name__)
SOURCE = "Home Assistant Dialogflow" SOURCE = "Home Assistant Dialogflow"
@ -83,16 +84,6 @@ async def async_unload_entry(hass, entry):
async_remove_entry = config_entry_flow.webhook_async_remove_entry async_remove_entry = config_entry_flow.webhook_async_remove_entry
config_entry_flow.register_webhook_flow(
DOMAIN,
'Dialogflow Webhook',
{
'dialogflow_url': 'https://dialogflow.com/docs/fulfillment#webhook',
'docs_url': 'https://www.home-assistant.io/components/dialogflow/'
}
)
def dialogflow_error_response(message, error): def dialogflow_error_response(message, error):
"""Return a response saying the error message.""" """Return a response saying the error message."""
dialogflow_response = DialogflowResponse(message['result']['parameters']) dialogflow_response = DialogflowResponse(message['result']['parameters'])

View File

@ -0,0 +1,13 @@
"""Config flow for DialogFlow."""
from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
config_entry_flow.register_webhook_flow(
DOMAIN,
'Dialogflow Webhook',
{
'dialogflow_url': 'https://dialogflow.com/docs/fulfillment#webhook',
'docs_url': 'https://www.home-assistant.io/components/dialogflow/'
}
)

View File

@ -0,0 +1,3 @@
"""Const for DialogFlow."""
DOMAIN = "dialogflow"

View File

@ -1,6 +1,7 @@
{ {
"domain": "dialogflow", "domain": "dialogflow",
"name": "Dialogflow", "name": "Dialogflow",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/dialogflow", "documentation": "https://www.home-assistant.io/components/dialogflow",
"requirements": [], "requirements": [],
"dependencies": [ "dependencies": [

View File

@ -1,6 +1,7 @@
{ {
"domain": "emulated_roku", "domain": "emulated_roku",
"name": "Emulated roku", "name": "Emulated roku",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/emulated_roku", "documentation": "https://www.home-assistant.io/components/emulated_roku",
"requirements": [ "requirements": [
"emulated_roku==0.1.8" "emulated_roku==0.1.8"

View File

@ -1,6 +1,7 @@
{ {
"domain": "esphome", "domain": "esphome",
"name": "ESPHome", "name": "ESPHome",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/esphome", "documentation": "https://www.home-assistant.io/components/esphome",
"requirements": [ "requirements": [
"aioesphomeapi==2.0.1" "aioesphomeapi==2.0.1"

View File

@ -12,10 +12,11 @@ from homeassistant.helpers import config_entry_flow
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.util import slugify from homeassistant.util import slugify
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'geofency'
CONF_MOBILE_BEACONS = 'mobile_beacons' CONF_MOBILE_BEACONS = 'mobile_beacons'
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
@ -134,12 +135,3 @@ async def async_unload_entry(hass, entry):
# pylint: disable=invalid-name # pylint: disable=invalid-name
async_remove_entry = config_entry_flow.webhook_async_remove_entry async_remove_entry = config_entry_flow.webhook_async_remove_entry
config_entry_flow.register_webhook_flow(
DOMAIN,
'Geofency Webhook',
{
'docs_url': 'https://www.home-assistant.io/components/geofency/'
}
)

View File

@ -0,0 +1,12 @@
"""Config flow for Geofency."""
from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
config_entry_flow.register_webhook_flow(
DOMAIN,
'Geofency Webhook',
{
'docs_url': 'https://www.home-assistant.io/components/geofency/'
}
)

View File

@ -0,0 +1,3 @@
"""Const for Geofency."""
DOMAIN = 'geofency'

View File

@ -1,6 +1,7 @@
{ {
"domain": "geofency", "domain": "geofency",
"name": "Geofency", "name": "Geofency",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/geofency", "documentation": "https://www.home-assistant.io/components/geofency",
"requirements": [], "requirements": [],
"dependencies": [ "dependencies": [

View File

@ -11,10 +11,10 @@ from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, \
from homeassistant.helpers import config_entry_flow from homeassistant.helpers import config_entry_flow
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'gpslogger'
TRACKER_UPDATE = '{}_tracker_update'.format(DOMAIN) TRACKER_UPDATE = '{}_tracker_update'.format(DOMAIN)
ATTR_ALTITUDE = 'altitude' ATTR_ALTITUDE = 'altitude'
@ -105,12 +105,3 @@ async def async_unload_entry(hass, entry):
# pylint: disable=invalid-name # pylint: disable=invalid-name
async_remove_entry = config_entry_flow.webhook_async_remove_entry async_remove_entry = config_entry_flow.webhook_async_remove_entry
config_entry_flow.register_webhook_flow(
DOMAIN,
'GPSLogger Webhook',
{
'docs_url': 'https://www.home-assistant.io/components/gpslogger/'
}
)

View File

@ -0,0 +1,12 @@
"""Config flow for GPSLogger."""
from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
config_entry_flow.register_webhook_flow(
DOMAIN,
'GPSLogger Webhook',
{
'docs_url': 'https://www.home-assistant.io/components/gpslogger/'
}
)

View File

@ -0,0 +1,3 @@
"""Const for GPSLogger."""
DOMAIN = 'gpslogger'

View File

@ -1,6 +1,7 @@
{ {
"domain": "gpslogger", "domain": "gpslogger",
"name": "Gpslogger", "name": "Gpslogger",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/gpslogger", "documentation": "https://www.home-assistant.io/components/gpslogger",
"requirements": [], "requirements": [],
"dependencies": [ "dependencies": [

View File

@ -1,6 +1,7 @@
{ {
"domain": "hangouts", "domain": "hangouts",
"name": "Hangouts", "name": "Hangouts",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/hangouts", "documentation": "https://www.home-assistant.io/components/hangouts",
"requirements": [ "requirements": [
"hangups==0.4.9" "hangups==0.4.9"

View File

@ -1,6 +1,7 @@
{ {
"domain": "heos", "domain": "heos",
"name": "HEOS", "name": "HEOS",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/heos", "documentation": "https://www.home-assistant.io/components/heos",
"requirements": [ "requirements": [
"pyheos==0.5.2" "pyheos==0.5.2"

View File

@ -1,6 +1,7 @@
{ {
"domain": "homekit_controller", "domain": "homekit_controller",
"name": "Homekit controller", "name": "Homekit controller",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/homekit_controller", "documentation": "https://www.home-assistant.io/components/homekit_controller",
"requirements": [ "requirements": [
"homekit[IP]==0.14.0" "homekit[IP]==0.14.0"

View File

@ -1,6 +1,7 @@
{ {
"domain": "homematicip_cloud", "domain": "homematicip_cloud",
"name": "Homematicip cloud", "name": "Homematicip cloud",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/homematicip_cloud", "documentation": "https://www.home-assistant.io/components/homematicip_cloud",
"requirements": [ "requirements": [
"homematicip==0.10.7" "homematicip==0.10.7"

View File

@ -1,6 +1,7 @@
{ {
"domain": "hue", "domain": "hue",
"name": "Philips Hue", "name": "Philips Hue",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/hue", "documentation": "https://www.home-assistant.io/components/hue",
"requirements": [ "requirements": [
"aiohue==1.9.1" "aiohue==1.9.1"

View File

@ -8,6 +8,7 @@ import voluptuous as vol
from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.helpers import config_entry_flow from homeassistant.helpers import config_entry_flow
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -21,8 +22,6 @@ ATTR_VALUE3 = 'value3'
CONF_KEY = 'key' CONF_KEY = 'key'
DOMAIN = 'ifttt'
SERVICE_TRIGGER = 'trigger' SERVICE_TRIGGER = 'trigger'
SERVICE_TRIGGER_SCHEMA = vol.Schema({ SERVICE_TRIGGER_SCHEMA = vol.Schema({
@ -108,13 +107,3 @@ async def async_unload_entry(hass, entry):
# pylint: disable=invalid-name # pylint: disable=invalid-name
async_remove_entry = config_entry_flow.webhook_async_remove_entry async_remove_entry = config_entry_flow.webhook_async_remove_entry
config_entry_flow.register_webhook_flow(
DOMAIN,
'IFTTT Webhook',
{
'applet_url': 'https://ifttt.com/maker_webhooks',
'docs_url': 'https://www.home-assistant.io/components/ifttt/'
}
)

View File

@ -0,0 +1,13 @@
"""Config flow for IFTTT."""
from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
config_entry_flow.register_webhook_flow(
DOMAIN,
'IFTTT Webhook',
{
'applet_url': 'https://ifttt.com/maker_webhooks',
'docs_url': 'https://www.home-assistant.io/components/ifttt/'
}
)

View File

@ -0,0 +1,3 @@
"""Const for IFTTT."""
DOMAIN = "ifttt"

View File

@ -1,6 +1,7 @@
{ {
"domain": "ifttt", "domain": "ifttt",
"name": "Ifttt", "name": "Ifttt",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/ifttt", "documentation": "https://www.home-assistant.io/components/ifttt",
"requirements": [ "requirements": [
"pyfttt==0.3" "pyfttt==0.3"

View File

@ -9,8 +9,7 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.const import HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR from homeassistant.const import HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import ( from homeassistant.helpers import config_validation as cv, discovery
config_entry_flow, config_validation as cv, discovery)
from homeassistant.util.json import load_json, save_json from homeassistant.util.json import load_json, save_json
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -279,8 +278,3 @@ class iOSIdentifyDeviceView(HomeAssistantView):
HTTP_INTERNAL_SERVER_ERROR) HTTP_INTERNAL_SERVER_ERROR)
return self.json({"status": "registered"}) return self.json({"status": "registered"})
config_entry_flow.register_discovery_flow(
DOMAIN, 'Home Assistant iOS', lambda *_: True,
config_entries.CONN_CLASS_CLOUD_PUSH)

View File

@ -0,0 +1,9 @@
"""Config flow for iOS."""
from homeassistant.helpers import config_entry_flow
from homeassistant import config_entries
from .const import DOMAIN
config_entry_flow.register_discovery_flow(
DOMAIN, 'Home Assistant iOS', lambda *_: True,
config_entries.CONN_CLASS_CLOUD_PUSH)

View File

@ -0,0 +1,3 @@
"""Const for iOS."""
DOMAIN = "ios"

View File

@ -1,6 +1,7 @@
{ {
"domain": "ios", "domain": "ios",
"name": "Ios", "name": "Ios",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/ios", "documentation": "https://www.home-assistant.io/components/ios",
"requirements": [], "requirements": [],
"dependencies": [ "dependencies": [

View File

@ -1,6 +1,7 @@
{ {
"domain": "ipma", "domain": "ipma",
"name": "Ipma", "name": "Ipma",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/ipma", "documentation": "https://www.home-assistant.io/components/ipma",
"requirements": [ "requirements": [
"pyipma==1.2.1" "pyipma==1.2.1"

View File

@ -1,6 +1,7 @@
{ {
"domain": "iqvia", "domain": "iqvia",
"name": "IQVIA", "name": "IQVIA",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/iqvia", "documentation": "https://www.home-assistant.io/components/iqvia",
"requirements": [ "requirements": [
"numpy==1.16.3", "numpy==1.16.3",

View File

@ -4,10 +4,10 @@ import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PORT from homeassistant.const import CONF_PORT
from homeassistant.helpers import config_entry_flow
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from .const import DOMAIN
DOMAIN = 'lifx'
CONF_SERVER = 'server' CONF_SERVER = 'server'
CONF_BROADCAST = 'broadcast' CONF_BROADCAST = 'broadcast'
@ -55,15 +55,3 @@ async def async_unload_entry(hass, entry):
await hass.config_entries.async_forward_entry_unload(entry, LIGHT_DOMAIN) await hass.config_entries.async_forward_entry_unload(entry, LIGHT_DOMAIN)
return True return True
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
import aiolifx
lifx_ip_addresses = await aiolifx.LifxScan(hass.loop).scan()
return len(lifx_ip_addresses) > 0
config_entry_flow.register_discovery_flow(
DOMAIN, 'LIFX', _async_has_devices, config_entries.CONN_CLASS_LOCAL_POLL)

View File

@ -0,0 +1,16 @@
"""Config flow flow LIFX."""
from homeassistant.helpers import config_entry_flow
from homeassistant import config_entries
from .const import DOMAIN
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
import aiolifx
lifx_ip_addresses = await aiolifx.LifxScan(hass.loop).scan()
return len(lifx_ip_addresses) > 0
config_entry_flow.register_discovery_flow(
DOMAIN, 'LIFX', _async_has_devices, config_entries.CONN_CLASS_LOCAL_POLL)

View File

@ -0,0 +1,3 @@
"""Const for LIFX."""
DOMAIN = 'lifx'

View File

@ -1,6 +1,7 @@
{ {
"domain": "lifx", "domain": "lifx",
"name": "Lifx", "name": "Lifx",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/lifx", "documentation": "https://www.home-assistant.io/components/lifx",
"requirements": [ "requirements": [
"aiolifx==0.6.7", "aiolifx==0.6.7",

View File

@ -145,12 +145,3 @@ async def async_unload_entry(hass, entry):
# pylint: disable=invalid-name # pylint: disable=invalid-name
async_remove_entry = config_entry_flow.webhook_async_remove_entry async_remove_entry = config_entry_flow.webhook_async_remove_entry
config_entry_flow.register_webhook_flow(
DOMAIN,
'Locative Webhook',
{
'docs_url': 'https://www.home-assistant.io/components/locative/'
}
)

View File

@ -0,0 +1,12 @@
"""Config flow for Locative."""
from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
config_entry_flow.register_webhook_flow(
DOMAIN,
'Locative Webhook',
{
'docs_url': 'https://www.home-assistant.io/components/locative/'
}
)

View File

@ -0,0 +1,3 @@
"""Const for Locative."""
DOMAIN = "locative"

View File

@ -1,6 +1,7 @@
{ {
"domain": "locative", "domain": "locative",
"name": "Locative", "name": "Locative",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/locative", "documentation": "https://www.home-assistant.io/components/locative",
"requirements": [], "requirements": [],
"dependencies": [ "dependencies": [

View File

@ -1,6 +1,7 @@
{ {
"domain": "logi_circle", "domain": "logi_circle",
"name": "Logi Circle", "name": "Logi Circle",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/logi_circle", "documentation": "https://www.home-assistant.io/components/logi_circle",
"requirements": ["logi_circle==0.2.2"], "requirements": ["logi_circle==0.2.2"],
"dependencies": ["ffmpeg"], "dependencies": ["ffmpeg"],

View File

@ -1,6 +1,7 @@
{ {
"domain": "luftdaten", "domain": "luftdaten",
"name": "Luftdaten", "name": "Luftdaten",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/luftdaten", "documentation": "https://www.home-assistant.io/components/luftdaten",
"requirements": [ "requirements": [
"luftdaten==0.3.4" "luftdaten==0.3.4"

View File

@ -10,12 +10,14 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_API_KEY, CONF_DOMAIN, CONF_WEBHOOK_ID from homeassistant.const import CONF_API_KEY, CONF_DOMAIN, CONF_WEBHOOK_ID
from homeassistant.helpers import config_entry_flow from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_SANDBOX = 'sandbox' CONF_SANDBOX = 'sandbox'
DEFAULT_SANDBOX = False DEFAULT_SANDBOX = False
DOMAIN = 'mailgun'
MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN) MESSAGE_RECEIVED = '{}_message_received'.format(DOMAIN)
@ -90,13 +92,3 @@ async def async_unload_entry(hass, entry):
# pylint: disable=invalid-name # pylint: disable=invalid-name
async_remove_entry = config_entry_flow.webhook_async_remove_entry async_remove_entry = config_entry_flow.webhook_async_remove_entry
config_entry_flow.register_webhook_flow(
DOMAIN,
'Mailgun Webhook',
{
'mailgun_url': 'https://documentation.mailgun.com/en/latest/user_manual.html#webhooks', # noqa: E501 pylint: disable=line-too-long
'docs_url': 'https://www.home-assistant.io/components/mailgun/'
}
)

View File

@ -0,0 +1,13 @@
"""Config flow for Mailgun."""
from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
config_entry_flow.register_webhook_flow(
DOMAIN,
'Mailgun Webhook',
{
'mailgun_url': 'https://documentation.mailgun.com/en/latest/user_manual.html#webhooks', # noqa: E501 pylint: disable=line-too-long
'docs_url': 'https://www.home-assistant.io/components/mailgun/'
}
)

View File

@ -0,0 +1,3 @@
"""Const for Mailgun."""
DOMAIN = "mailgun"

View File

@ -1,6 +1,7 @@
{ {
"domain": "mailgun", "domain": "mailgun",
"name": "Mailgun", "name": "Mailgun",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/mailgun", "documentation": "https://www.home-assistant.io/components/mailgun",
"requirements": [ "requirements": [
"pymailgunner==1.4" "pymailgunner==1.4"

View File

@ -1,5 +1,4 @@
"""Integrates Native Apps to Home Assistant.""" """Integrates Native Apps to Home Assistant."""
from homeassistant import config_entries
from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.components.webhook import async_register as webhook_register from homeassistant.components.webhook import async_register as webhook_register
from homeassistant.helpers import device_registry as dr, discovery from homeassistant.helpers import device_registry as dr, discovery
@ -91,26 +90,3 @@ async def async_setup_entry(hass, entry):
hass.config_entries.async_forward_entry_setup(entry, DATA_SENSOR)) hass.config_entries.async_forward_entry_setup(entry, DATA_SENSOR))
return True return True
@config_entries.HANDLERS.register(DOMAIN)
class MobileAppFlowHandler(config_entries.ConfigFlow):
"""Handle a Mobile App config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
placeholders = {
'apps_url':
'https://www.home-assistant.io/components/mobile_app/#apps'
}
return self.async_abort(reason='install_app',
description_placeholders=placeholders)
async def async_step_registration(self, user_input=None):
"""Handle a flow initialized during registration."""
return self.async_create_entry(title=user_input[ATTR_DEVICE_NAME],
data=user_input)

View File

@ -0,0 +1,26 @@
"""Config flow for Mobile App."""
from homeassistant import config_entries
from .const import DOMAIN, ATTR_DEVICE_NAME
@config_entries.HANDLERS.register(DOMAIN)
class MobileAppFlowHandler(config_entries.ConfigFlow):
"""Handle a Mobile App config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
placeholders = {
'apps_url':
'https://www.home-assistant.io/components/mobile_app/#apps'
}
return self.async_abort(reason='install_app',
description_placeholders=placeholders)
async def async_step_registration(self, user_input=None):
"""Handle a flow initialized during registration."""
return self.async_create_entry(title=user_input[ATTR_DEVICE_NAME],
data=user_input)

View File

@ -1,6 +1,7 @@
{ {
"domain": "mobile_app", "domain": "mobile_app",
"name": "Home Assistant Mobile App Support", "name": "Home Assistant Mobile App Support",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/mobile_app", "documentation": "https://www.home-assistant.io/components/mobile_app",
"requirements": [ "requirements": [
"PyNaCl==1.3.0" "PyNaCl==1.3.0"

View File

@ -1,6 +1,7 @@
{ {
"domain": "mqtt", "domain": "mqtt",
"name": "MQTT", "name": "MQTT",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/mqtt", "documentation": "https://www.home-assistant.io/components/mqtt",
"requirements": [ "requirements": [
"hbmqtt==0.9.4", "hbmqtt==0.9.4",

View File

@ -1,6 +1,7 @@
{ {
"domain": "nest", "domain": "nest",
"name": "Nest", "name": "Nest",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/nest", "documentation": "https://www.home-assistant.io/components/nest",
"requirements": [ "requirements": [
"python-nest==4.1.0" "python-nest==4.1.0"

View File

@ -1,6 +1,7 @@
{ {
"domain": "openuv", "domain": "openuv",
"name": "Openuv", "name": "Openuv",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/openuv", "documentation": "https://www.home-assistant.io/components/openuv",
"requirements": [ "requirements": [
"pyopenuv==1.0.9" "pyopenuv==1.0.9"

View File

@ -1,6 +1,7 @@
{ {
"domain": "owntracks", "domain": "owntracks",
"name": "Owntracks", "name": "Owntracks",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/owntracks", "documentation": "https://www.home-assistant.io/components/owntracks",
"requirements": [ "requirements": [
"PyNaCl==1.3.0" "PyNaCl==1.3.0"

View File

@ -1,6 +1,7 @@
{ {
"domain": "point", "domain": "point",
"name": "Point", "name": "Point",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/point", "documentation": "https://www.home-assistant.io/components/point",
"requirements": [ "requirements": [
"pypoint==1.1.1" "pypoint==1.1.1"

View File

@ -1,6 +1,7 @@
{ {
"domain": "ps4", "domain": "ps4",
"name": "Ps4", "name": "Ps4",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/ps4", "documentation": "https://www.home-assistant.io/components/ps4",
"requirements": [ "requirements": [
"pyps4-homeassistant==0.7.3" "pyps4-homeassistant==0.7.3"

View File

@ -1,6 +1,7 @@
{ {
"domain": "rainmachine", "domain": "rainmachine",
"name": "Rainmachine", "name": "Rainmachine",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/rainmachine", "documentation": "https://www.home-assistant.io/components/rainmachine",
"requirements": [ "requirements": [
"regenmaschine==1.4.0" "regenmaschine==1.4.0"

View File

@ -1,6 +1,7 @@
{ {
"domain": "simplisafe", "domain": "simplisafe",
"name": "Simplisafe", "name": "Simplisafe",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/simplisafe", "documentation": "https://www.home-assistant.io/components/simplisafe",
"requirements": [ "requirements": [
"simplisafe-python==3.4.1" "simplisafe-python==3.4.1"

View File

@ -1,6 +1,7 @@
{ {
"domain": "smartthings", "domain": "smartthings",
"name": "Smartthings", "name": "Smartthings",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/smartthings", "documentation": "https://www.home-assistant.io/components/smartthings",
"requirements": [ "requirements": [
"pysmartapp==0.3.2", "pysmartapp==0.3.2",

View File

@ -1,6 +1,7 @@
{ {
"domain": "smhi", "domain": "smhi",
"name": "Smhi", "name": "Smhi",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/smhi", "documentation": "https://www.home-assistant.io/components/smhi",
"requirements": [ "requirements": [
"smhi-pkg==1.0.10" "smhi-pkg==1.0.10"

View File

@ -5,10 +5,11 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.const import CONF_HOSTS, ATTR_ENTITY_ID, ATTR_TIME from homeassistant.const import CONF_HOSTS, ATTR_ENTITY_ID, ATTR_TIME
from homeassistant.helpers import config_entry_flow, config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
DOMAIN = 'sonos' from .const import DOMAIN
CONF_ADVERTISE_ADDR = 'advertise_addr' CONF_ADVERTISE_ADDR = 'advertise_addr'
CONF_INTERFACE_ADDR = 'interface_addr' CONF_INTERFACE_ADDR = 'interface_addr'
@ -141,14 +142,3 @@ async def async_setup_entry(hass, entry):
hass.async_create_task(hass.config_entries.async_forward_entry_setup( hass.async_create_task(hass.config_entries.async_forward_entry_setup(
entry, MP_DOMAIN)) entry, MP_DOMAIN))
return True return True
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
import pysonos
return await hass.async_add_executor_job(pysonos.discover)
config_entry_flow.register_discovery_flow(
DOMAIN, 'Sonos', _async_has_devices, config_entries.CONN_CLASS_LOCAL_PUSH)

View File

@ -0,0 +1,15 @@
"""Config flow for SONOS."""
from homeassistant.helpers import config_entry_flow
from homeassistant import config_entries
from .const import DOMAIN
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
import pysonos
return await hass.async_add_executor_job(pysonos.discover)
config_entry_flow.register_discovery_flow(
DOMAIN, 'Sonos', _async_has_devices, config_entries.CONN_CLASS_LOCAL_PUSH)

View File

@ -0,0 +1,3 @@
"""Const for Sonos."""
DOMAIN = "sonos"

View File

@ -1,6 +1,7 @@
{ {
"domain": "sonos", "domain": "sonos",
"name": "Sonos", "name": "Sonos",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/sonos", "documentation": "https://www.home-assistant.io/components/sonos",
"requirements": [ "requirements": [
"pysonos==0.0.12" "pysonos==0.0.12"

View File

@ -1,6 +1,7 @@
{ {
"domain": "tellduslive", "domain": "tellduslive",
"name": "Tellduslive", "name": "Tellduslive",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/tellduslive", "documentation": "https://www.home-assistant.io/components/tellduslive",
"requirements": [ "requirements": [
"tellduslive==0.10.10" "tellduslive==0.10.10"

View File

@ -1,6 +1,7 @@
{ {
"domain": "toon", "domain": "toon",
"name": "Toon", "name": "Toon",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/toon", "documentation": "https://www.home-assistant.io/components/toon",
"requirements": [ "requirements": [
"toonapilib==3.2.2" "toonapilib==3.2.2"

View File

@ -5,14 +5,12 @@ import voluptuous as vol
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.helpers import config_entry_flow
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .config_flow import async_get_devices
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'tplink'
TPLINK_HOST_SCHEMA = vol.Schema({ TPLINK_HOST_SCHEMA = vol.Schema({
vol.Required(CONF_HOST): cv.string vol.Required(CONF_HOST): cv.string
}) })
@ -34,16 +32,6 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
from pyHS100 import Discover
def discover():
devs = Discover.discover()
return devs
return await hass.async_add_executor_job(discover)
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the TP-Link component.""" """Set up the TP-Link component."""
conf = config.get(DOMAIN) conf = config.get(DOMAIN)
@ -74,7 +62,7 @@ async def async_setup_entry(hass, config_entry):
# If initialized from configure integrations, there's no config # If initialized from configure integrations, there's no config
# so we default here to True # so we default here to True
if config_data is None or config_data[CONF_DISCOVERY]: if config_data is None or config_data[CONF_DISCOVERY]:
devs = await _async_has_devices(hass) devs = await async_get_devices(hass)
_LOGGER.info("Discovered %s TP-Link smart home device(s)", len(devs)) _LOGGER.info("Discovered %s TP-Link smart home device(s)", len(devs))
devices.update(devs) devices.update(devs)
@ -149,9 +137,3 @@ async def async_unload_entry(hass, entry):
# We were not able to unload the platforms, either because there # We were not able to unload the platforms, either because there
# were none or one of the forward_unloads failed. # were none or one of the forward_unloads failed.
return False return False
config_entry_flow.register_discovery_flow(DOMAIN,
'TP-Link Smart Home',
_async_has_devices,
config_entries.CONN_CLASS_LOCAL_POLL)

View File

@ -0,0 +1,20 @@
"""Config flow for TP-Link."""
from homeassistant.helpers import config_entry_flow
from homeassistant import config_entries
from .const import DOMAIN
async def async_get_devices(hass):
"""Return if there are devices that can be discovered."""
from pyHS100 import Discover
def discover():
devs = Discover.discover()
return devs
return await hass.async_add_executor_job(discover)
config_entry_flow.register_discovery_flow(DOMAIN,
'TP-Link Smart Home',
async_get_devices,
config_entries.CONN_CLASS_LOCAL_POLL)

View File

@ -0,0 +1,3 @@
"""Const for TP-Link."""
DOMAIN = "tplink"

View File

@ -1,6 +1,7 @@
{ {
"domain": "tplink", "domain": "tplink",
"name": "Tplink", "name": "Tplink",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/tplink", "documentation": "https://www.home-assistant.io/components/tplink",
"requirements": [ "requirements": [
"pyHS100==0.3.5", "pyHS100==0.3.5",

View File

@ -1,6 +1,7 @@
{ {
"domain": "tradfri", "domain": "tradfri",
"name": "Tradfri", "name": "Tradfri",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/tradfri", "documentation": "https://www.home-assistant.io/components/tradfri",
"requirements": [ "requirements": [
"pytradfri[async]==6.0.1" "pytradfri[async]==6.0.1"

View File

@ -4,8 +4,7 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.helpers import config_entry_flow from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
DOMAIN = 'twilio'
CONF_ACCOUNT_SID = 'account_sid' CONF_ACCOUNT_SID = 'account_sid'
CONF_AUTH_TOKEN = 'auth_token' CONF_AUTH_TOKEN = 'auth_token'
@ -60,14 +59,3 @@ async def async_unload_entry(hass, entry):
# pylint: disable=invalid-name # pylint: disable=invalid-name
async_remove_entry = config_entry_flow.webhook_async_remove_entry async_remove_entry = config_entry_flow.webhook_async_remove_entry
config_entry_flow.register_webhook_flow(
DOMAIN,
'Twilio Webhook',
{
'twilio_url':
'https://www.twilio.com/docs/glossary/what-is-a-webhook',
'docs_url': 'https://www.home-assistant.io/components/twilio/'
}
)

View File

@ -0,0 +1,15 @@
"""Config flow for Twilio."""
from homeassistant.helpers import config_entry_flow
from .const import DOMAIN
config_entry_flow.register_webhook_flow(
DOMAIN,
'Twilio Webhook',
{
'twilio_url':
'https://www.twilio.com/docs/glossary/what-is-a-webhook',
'docs_url': 'https://www.home-assistant.io/components/twilio/'
}
)

View File

@ -0,0 +1,3 @@
"""Const for Twilio."""
DOMAIN = "twilio"

View File

@ -1,6 +1,7 @@
{ {
"domain": "twilio", "domain": "twilio",
"name": "Twilio", "name": "Twilio",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/twilio", "documentation": "https://www.home-assistant.io/components/twilio",
"requirements": [ "requirements": [
"twilio==6.19.1" "twilio==6.19.1"

View File

@ -1,20 +1,9 @@
"""Support for devices connected to UniFi POE.""" """Support for devices connected to UniFi POE."""
import voluptuous as vol from homeassistant.const import CONF_HOST
from homeassistant import config_entries
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME, CONF_VERIFY_SSL)
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from .const import (CONF_CONTROLLER, CONF_POE_CONTROL, CONF_SITE_ID, from .const import CONF_CONTROLLER, CONF_SITE_ID, CONTROLLER_ID, DOMAIN
CONTROLLER_ID, DOMAIN, LOGGER) from .controller import UniFiController
from .controller import UniFiController, get_controller
from .errors import (
AlreadyConfigured, AuthenticationRequired, CannotConnect, UserLevel)
DEFAULT_PORT = 8443
DEFAULT_SITE_ID = 'default'
DEFAULT_VERIFY_SSL = False
async def async_setup(hass, config): async def async_setup(hass, config):
@ -64,116 +53,3 @@ async def async_unload_entry(hass, config_entry):
) )
controller = hass.data[DOMAIN].pop(controller_id) controller = hass.data[DOMAIN].pop(controller_id)
return await controller.async_reset() return await controller.async_reset()
@config_entries.HANDLERS.register(DOMAIN)
class UnifiFlowHandler(config_entries.ConfigFlow):
"""Handle a UniFi config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
def __init__(self):
"""Initialize the UniFi flow."""
self.config = None
self.desc = None
self.sites = None
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
errors = {}
if user_input is not None:
try:
self.config = {
CONF_HOST: user_input[CONF_HOST],
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_PORT: user_input.get(CONF_PORT),
CONF_VERIFY_SSL: user_input.get(CONF_VERIFY_SSL),
CONF_SITE_ID: DEFAULT_SITE_ID,
}
controller = await get_controller(self.hass, **self.config)
self.sites = await controller.sites()
return await self.async_step_site()
except AuthenticationRequired:
errors['base'] = 'faulty_credentials'
except CannotConnect:
errors['base'] = 'service_unavailable'
except Exception: # pylint: disable=broad-except
LOGGER.error(
'Unknown error connecting with UniFi Controller at %s',
user_input[CONF_HOST])
return self.async_abort(reason='unknown')
return self.async_show_form(
step_id='user',
data_schema=vol.Schema({
vol.Required(CONF_HOST): str,
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
vol.Optional(
CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
}),
errors=errors,
)
async def async_step_site(self, user_input=None):
"""Select site to control."""
errors = {}
if user_input is not None:
try:
desc = user_input.get(CONF_SITE_ID, self.desc)
for site in self.sites.values():
if desc == site['desc']:
if site['role'] != 'admin':
raise UserLevel
self.config[CONF_SITE_ID] = site['name']
break
for entry in self._async_current_entries():
controller = entry.data[CONF_CONTROLLER]
if controller[CONF_HOST] == self.config[CONF_HOST] and \
controller[CONF_SITE_ID] == self.config[CONF_SITE_ID]:
raise AlreadyConfigured
data = {
CONF_CONTROLLER: self.config,
CONF_POE_CONTROL: True
}
return self.async_create_entry(
title=desc,
data=data
)
except AlreadyConfigured:
return self.async_abort(reason='already_configured')
except UserLevel:
return self.async_abort(reason='user_privilege')
if len(self.sites) == 1:
self.desc = next(iter(self.sites.values()))['desc']
return await self.async_step_site(user_input={})
sites = []
for site in self.sites.values():
sites.append(site['desc'])
return self.async_show_form(
step_id='site',
data_schema=vol.Schema({
vol.Required(CONF_SITE_ID): vol.In(sites)
}),
errors=errors,
)

View File

@ -0,0 +1,130 @@
"""Config flow for Unifi."""
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME, CONF_VERIFY_SSL)
from .const import (CONF_CONTROLLER, CONF_POE_CONTROL, CONF_SITE_ID,
DOMAIN, LOGGER)
from .controller import get_controller
from .errors import (
AlreadyConfigured, AuthenticationRequired, CannotConnect, UserLevel)
DEFAULT_PORT = 8443
DEFAULT_SITE_ID = 'default'
DEFAULT_VERIFY_SSL = False
@config_entries.HANDLERS.register(DOMAIN)
class UnifiFlowHandler(config_entries.ConfigFlow):
"""Handle a UniFi config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
def __init__(self):
"""Initialize the UniFi flow."""
self.config = None
self.desc = None
self.sites = None
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
errors = {}
if user_input is not None:
try:
self.config = {
CONF_HOST: user_input[CONF_HOST],
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_PORT: user_input.get(CONF_PORT),
CONF_VERIFY_SSL: user_input.get(CONF_VERIFY_SSL),
CONF_SITE_ID: DEFAULT_SITE_ID,
}
controller = await get_controller(self.hass, **self.config)
self.sites = await controller.sites()
return await self.async_step_site()
except AuthenticationRequired:
errors['base'] = 'faulty_credentials'
except CannotConnect:
errors['base'] = 'service_unavailable'
except Exception: # pylint: disable=broad-except
LOGGER.error(
'Unknown error connecting with UniFi Controller at %s',
user_input[CONF_HOST])
return self.async_abort(reason='unknown')
return self.async_show_form(
step_id='user',
data_schema=vol.Schema({
vol.Required(CONF_HOST): str,
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
vol.Optional(
CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
}),
errors=errors,
)
async def async_step_site(self, user_input=None):
"""Select site to control."""
errors = {}
if user_input is not None:
try:
desc = user_input.get(CONF_SITE_ID, self.desc)
for site in self.sites.values():
if desc == site['desc']:
if site['role'] != 'admin':
raise UserLevel
self.config[CONF_SITE_ID] = site['name']
break
for entry in self._async_current_entries():
controller = entry.data[CONF_CONTROLLER]
if controller[CONF_HOST] == self.config[CONF_HOST] and \
controller[CONF_SITE_ID] == self.config[CONF_SITE_ID]:
raise AlreadyConfigured
data = {
CONF_CONTROLLER: self.config,
CONF_POE_CONTROL: True
}
return self.async_create_entry(
title=desc,
data=data
)
except AlreadyConfigured:
return self.async_abort(reason='already_configured')
except UserLevel:
return self.async_abort(reason='user_privilege')
if len(self.sites) == 1:
self.desc = next(iter(self.sites.values()))['desc']
return await self.async_step_site(user_input={})
sites = []
for site in self.sites.values():
sites.append(site['desc'])
return self.async_show_form(
step_id='site',
data_schema=vol.Schema({
vol.Required(CONF_SITE_ID): vol.In(sites)
}),
errors=errors,
)

View File

@ -1,6 +1,7 @@
{ {
"domain": "unifi", "domain": "unifi",
"name": "Unifi", "name": "Unifi",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/unifi", "documentation": "https://www.home-assistant.io/components/unifi",
"requirements": [ "requirements": [
"aiounifi==4", "aiounifi==4",

View File

@ -7,7 +7,6 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import config_entry_flow
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import dispatcher from homeassistant.helpers import dispatcher
@ -204,10 +203,3 @@ async def async_unload_entry(hass: HomeAssistantType,
dispatcher.async_dispatcher_send(hass, SIGNAL_REMOVE_SENSOR, device) dispatcher.async_dispatcher_send(hass, SIGNAL_REMOVE_SENSOR, device)
return True return True
config_entry_flow.register_discovery_flow(
DOMAIN,
'UPnP/IGD',
Device.async_discover,
config_entries.CONN_CLASS_LOCAL_POLL)

View File

@ -0,0 +1,13 @@
"""Config flow for UPNP."""
from homeassistant.helpers import config_entry_flow
from homeassistant import config_entries
from .const import DOMAIN
from .device import Device
config_entry_flow.register_discovery_flow(
DOMAIN,
'UPnP/IGD',
Device.async_discover,
config_entries.CONN_CLASS_LOCAL_POLL)

View File

@ -1,6 +1,7 @@
{ {
"domain": "upnp", "domain": "upnp",
"name": "Upnp", "name": "Upnp",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/upnp", "documentation": "https://www.home-assistant.io/components/upnp",
"requirements": [ "requirements": [
"async-upnp-client==0.14.7" "async-upnp-client==0.14.7"

View File

@ -1,6 +1,7 @@
{ {
"domain": "zha", "domain": "zha",
"name": "Zigbee Home Automation", "name": "Zigbee Home Automation",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/zha", "documentation": "https://www.home-assistant.io/components/zha",
"requirements": [ "requirements": [
"bellows-homeassistant==0.7.3", "bellows-homeassistant==0.7.3",

View File

@ -1,6 +1,7 @@
{ {
"domain": "zone", "domain": "zone",
"name": "Zone", "name": "Zone",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/zone", "documentation": "https://www.home-assistant.io/components/zone",
"requirements": [], "requirements": [],
"dependencies": [], "dependencies": [],

View File

@ -1,6 +1,7 @@
{ {
"domain": "zwave", "domain": "zwave",
"name": "Z-Wave", "name": "Z-Wave",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/zwave", "documentation": "https://www.home-assistant.io/components/zwave",
"requirements": [ "requirements": [
"homeassistant-pyozw==0.1.4", "homeassistant-pyozw==0.1.4",

View File

@ -140,57 +140,6 @@ SOURCE_DISCOVERY = 'discovery'
SOURCE_IMPORT = 'import' SOURCE_IMPORT = 'import'
HANDLERS = Registry() HANDLERS = Registry()
# Components that have config flows. In future we will auto-generate this list.
FLOWS = [
'ambiclimate',
'ambient_station',
'axis',
'cast',
'daikin',
'deconz',
'dialogflow',
'esphome',
'emulated_roku',
'geofency',
'gpslogger',
'hangouts',
'heos',
'homekit_controller',
'homematicip_cloud',
'hue',
'ifttt',
'ios',
'ipma',
'iqvia',
'lifx',
'locative',
'logi_circle',
'luftdaten',
'mailgun',
'mobile_app',
'mqtt',
'nest',
'openuv',
'owntracks',
'point',
'ps4',
'rainmachine',
'simplisafe',
'smartthings',
'smhi',
'sonos',
'tellduslive',
'toon',
'tplink',
'tradfri',
'twilio',
'unifi',
'upnp',
'zha',
'zone',
'zwave',
]
STORAGE_KEY = 'core.config_entries' STORAGE_KEY = 'core.config_entries'
STORAGE_VERSION = 1 STORAGE_VERSION = 1
@ -299,7 +248,17 @@ class ConfigEntry:
if integration is None: if integration is None:
integration = await loader.async_get_integration(hass, self.domain) integration = await loader.async_get_integration(hass, self.domain)
component = integration.get_component() try:
component = integration.get_component()
if self.domain == integration.domain:
integration.get_platform('config_flow')
except ImportError as err:
_LOGGER.error(
'Error importing integration %s to set up %s config entry: %s',
integration.domain, self.domain, err)
if self.domain == integration.domain:
self.state = ENTRY_STATE_SETUP_ERROR
return
# Perform migration # Perform migration
if integration.domain == self.domain: if integration.domain == self.domain:
@ -422,7 +381,8 @@ class ConfigEntry:
if self.version == handler.VERSION: if self.version == handler.VERSION:
return True return True
component = getattr(hass.components, self.domain) integration = await loader.async_get_integration(hass, self.domain)
component = integration.get_component()
supports_migrate = hasattr(component, 'async_migrate_entry') supports_migrate = hasattr(component, 'async_migrate_entry')
if not supports_migrate: if not supports_migrate:
_LOGGER.error("Migration handler not found for entry %s for %s", _LOGGER.error("Migration handler not found for entry %s for %s",
@ -430,7 +390,9 @@ class ConfigEntry:
return False return False
try: try:
result = await component.async_migrate_entry(hass, self) result = await component.async_migrate_entry( # type: ignore
hass, self
)
if not isinstance(result, bool): if not isinstance(result, bool):
_LOGGER.error('%s.async_migrate_entry did not return boolean', _LOGGER.error('%s.async_migrate_entry did not return boolean',
self.domain) self.domain)
@ -441,7 +403,7 @@ class ConfigEntry:
return result return result
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error migrating entry %s for %s', _LOGGER.exception('Error migrating entry %s for %s',
self.title, component.DOMAIN) self.title, self.domain)
return False return False
def add_update_listener(self, listener: Callable) -> Callable: def add_update_listener(self, listener: Callable) -> Callable:
@ -714,10 +676,10 @@ class ConfigEntries:
self.hass, self._hass_config, integration) self.hass, self._hass_config, integration)
try: try:
integration.get_component() integration.get_platform('config_flow')
except ImportError as err: except ImportError as err:
_LOGGER.error( _LOGGER.error(
'Error occurred while loading integration %s: %s', 'Error occurred loading config flow for integration %s: %s',
handler_key, err) handler_key, err)
raise data_entry_flow.UnknownHandler raise data_entry_flow.UnknownHandler

View File

@ -0,0 +1,55 @@
"""Automatically generated by hassfest.
To update, run python3 -m hassfest
"""
FLOWS = [
"ambiclimate",
"ambient_station",
"axis",
"cast",
"daikin",
"deconz",
"dialogflow",
"emulated_roku",
"esphome",
"geofency",
"gpslogger",
"hangouts",
"heos",
"homekit_controller",
"homematicip_cloud",
"hue",
"ifttt",
"ios",
"ipma",
"iqvia",
"lifx",
"locative",
"logi_circle",
"luftdaten",
"mailgun",
"mobile_app",
"mqtt",
"nest",
"openuv",
"owntracks",
"point",
"ps4",
"rainmachine",
"simplisafe",
"smartthings",
"smhi",
"sonos",
"tellduslive",
"toon",
"tplink",
"tradfri",
"twilio",
"unifi",
"upnp",
"zha",
"zone",
"zwave"
]

View File

@ -2,9 +2,9 @@
import logging import logging
from typing import Any, Dict, Iterable, Optional from typing import Any, Dict, Iterable, Optional
from homeassistant import config_entries
from homeassistant.loader import async_get_integration, bind_hass from homeassistant.loader import async_get_integration, bind_hass
from homeassistant.util.json import load_json from homeassistant.util.json import load_json
from homeassistant.generated import config_flows
from .typing import HomeAssistantType from .typing import HomeAssistantType
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -106,7 +106,7 @@ async def async_get_component_resources(hass: HomeAssistantType,
translation_cache = hass.data[TRANSLATION_STRING_CACHE][language] translation_cache = hass.data[TRANSLATION_STRING_CACHE][language]
# Get the set of components # Get the set of components
components = hass.config.components | set(config_entries.FLOWS) components = hass.config.components | set(config_flows.FLOWS)
# Calculate the missing components # Calculate the missing components
missing_components = components - set(translation_cache) missing_components = components - set(translation_cache)

View File

@ -1,3 +1,9 @@
[MASTER]
ignore=tests
[BASIC]
good-names=i,j,k,ex,Run,_,fp
[MESSAGES CONTROL] [MESSAGES CONTROL]
# Reasons disabled: # Reasons disabled:
# locally-disabled - it spams too much # locally-disabled - it spams too much

View File

@ -3,13 +3,14 @@ import pathlib
import sys import sys
from .model import Integration, Config from .model import Integration, Config
from . import dependencies, manifest, codeowners, services from . import dependencies, manifest, codeowners, services, config_flow
PLUGINS = [ PLUGINS = [
manifest, manifest,
dependencies, dependencies,
codeowners, codeowners,
services, services,
config_flow,
] ]

View File

@ -0,0 +1,85 @@
"""Generate config flow file."""
import json
from typing import Dict
from .model import Integration, Config
BASE = """
\"\"\"Automatically generated by hassfest.
To update, run python3 -m hassfest
\"\"\"
FLOWS = {}
""".strip()
def validate_integration(integration: Integration):
"""Validate we can load config flow without installing requirements."""
if not (integration.path / "config_flow.py").is_file():
integration.add_error(
'config_flow',
"Config flows need to be defined in the file config_flow.py")
# Currently not require being able to load config flow without
# installing requirements.
# try:
# integration.import_pkg('config_flow')
# except ImportError as err:
# integration.add_error(
# 'config_flow',
# "Unable to import config flow: {}. Config flows should be able "
# "to be imported without installing requirements.".format(err))
# return
# if integration.domain not in config_entries.HANDLERS:
# integration.add_error(
# 'config_flow',
# "Importing the config flow platform did not register a config "
# "flow handler.")
def generate_and_validate(integrations: Dict[str, Integration]):
"""Validate and generate config flow data."""
domains = []
for domain in sorted(integrations):
integration = integrations[domain]
if not integration.manifest:
continue
config_flow = integration.manifest.get('config_flow')
if not config_flow:
continue
validate_integration(integration)
domains.append(domain)
return BASE.format(json.dumps(domains, indent=4))
def validate(integrations: Dict[str, Integration], config: Config):
"""Validate config flow file."""
config_flow_path = config.root / 'homeassistant/generated/config_flows.py'
config.cache['config_flow'] = content = generate_and_validate(integrations)
with open(str(config_flow_path), 'r') as fp:
if fp.read().strip() != content:
config.add_error(
"config_flow",
"File config_flows.py is not up to date. "
"Run python3 -m script.hassfest",
fixable=True
)
return
def generate(integrations: Dict[str, Integration], config: Config):
"""Generate config flow file."""
config_flow_path = config.root / 'homeassistant/generated/config_flows.py'
with open(str(config_flow_path), 'w') as fp:
fp.write(config.cache['config_flow'] + '\n')

View File

@ -10,6 +10,7 @@ from .model import Integration
MANIFEST_SCHEMA = vol.Schema({ MANIFEST_SCHEMA = vol.Schema({
vol.Required('domain'): str, vol.Required('domain'): str,
vol.Required('name'): str, vol.Required('name'): str,
vol.Optional('config_flow'): bool,
vol.Required('documentation'): str, vol.Required('documentation'): str,
vol.Required('requirements'): [str], vol.Required('requirements'): [str],
vol.Required('dependencies'): [str], vol.Required('dependencies'): [str],

View File

@ -2,6 +2,7 @@
import json import json
from typing import List, Dict, Any from typing import List, Dict, Any
import pathlib import pathlib
import importlib
import attr import attr
@ -92,3 +93,10 @@ class Integration:
return return
self.manifest = manifest self.manifest = manifest
def import_pkg(self, platform=None):
"""Import the Python file."""
pkg = "homeassistant.components.{}".format(self.domain)
if platform is not None:
pkg += ".{}".format(platform)
return importlib.import_module(pkg)

View File

@ -926,7 +926,7 @@ async def get_system_health_info(hass, domain):
def mock_integration(hass, module): def mock_integration(hass, module):
"""Mock an integration.""" """Mock an integration."""
integration = loader.Integration( integration = loader.Integration(
hass, 'homeassisant.components.{}'.format(module.DOMAIN), None, hass, 'homeassistant.components.{}'.format(module.DOMAIN), None,
module.mock_manifest()) module.mock_manifest())
_LOGGER.info("Adding mock integration: %s", module.DOMAIN) _LOGGER.info("Adding mock integration: %s", module.DOMAIN)

View File

@ -12,9 +12,11 @@ from homeassistant.config_entries import HANDLERS
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.components.config import config_entries from homeassistant.components.config import config_entries
from homeassistant.generated import config_flows
from tests.common import ( from tests.common import (
MockConfigEntry, MockModule, mock_coro_func, mock_integration) MockConfigEntry, MockModule, mock_coro_func, mock_integration,
mock_entity_platform)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -121,7 +123,7 @@ async def test_remove_entry_unauth(hass, client, hass_admin_user):
@asyncio.coroutine @asyncio.coroutine
def test_available_flows(hass, client): def test_available_flows(hass, client):
"""Test querying the available flows.""" """Test querying the available flows."""
with patch.object(core_ce, 'FLOWS', ['hello', 'world']): with patch.object(config_flows, 'FLOWS', ['hello', 'world']):
resp = yield from client.get( resp = yield from client.get(
'/api/config/config_entries/flow_handlers') '/api/config/config_entries/flow_handlers')
assert resp.status == 200 assert resp.status == 200
@ -137,6 +139,8 @@ def test_available_flows(hass, client):
@asyncio.coroutine @asyncio.coroutine
def test_initialize_flow(hass, client): def test_initialize_flow(hass, client):
"""Test we can initialize a flow.""" """Test we can initialize a flow."""
mock_entity_platform(hass, 'config_flow.test', None)
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
@asyncio.coroutine @asyncio.coroutine
def async_step_user(self, user_input=None): def async_step_user(self, user_input=None):
@ -221,6 +225,8 @@ async def test_initialize_flow_unauth(hass, client, hass_admin_user):
@asyncio.coroutine @asyncio.coroutine
def test_abort(hass, client): def test_abort(hass, client):
"""Test a flow that aborts.""" """Test a flow that aborts."""
mock_entity_platform(hass, 'config_flow.test', None)
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
@asyncio.coroutine @asyncio.coroutine
def async_step_user(self, user_input=None): def async_step_user(self, user_input=None):
@ -244,6 +250,8 @@ def test_abort(hass, client):
@asyncio.coroutine @asyncio.coroutine
def test_create_account(hass, client): def test_create_account(hass, client):
"""Test a flow that creates an account.""" """Test a flow that creates an account."""
mock_entity_platform(hass, 'config_flow.test', None)
mock_integration( mock_integration(
hass, hass,
MockModule('test', async_setup_entry=mock_coro_func(True))) MockModule('test', async_setup_entry=mock_coro_func(True)))
@ -286,6 +294,7 @@ def test_two_step_flow(hass, client):
mock_integration( mock_integration(
hass, hass,
MockModule('test', async_setup_entry=mock_coro_func(True))) MockModule('test', async_setup_entry=mock_coro_func(True)))
mock_entity_platform(hass, 'config_flow.test', None)
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
VERSION = 1 VERSION = 1
@ -352,6 +361,7 @@ async def test_continue_flow_unauth(hass, client, hass_admin_user):
mock_integration( mock_integration(
hass, hass,
MockModule('test', async_setup_entry=mock_coro_func(True))) MockModule('test', async_setup_entry=mock_coro_func(True)))
mock_entity_platform(hass, 'config_flow.test', None)
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
VERSION = 1 VERSION = 1
@ -402,6 +412,8 @@ async def test_continue_flow_unauth(hass, client, hass_admin_user):
@asyncio.coroutine @asyncio.coroutine
def test_get_progress_index(hass, client): def test_get_progress_index(hass, client):
"""Test querying for the flows that are in progress.""" """Test querying for the flows that are in progress."""
mock_entity_platform(hass, 'config_flow.test', None)
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
VERSION = 5 VERSION = 5
@ -441,6 +453,8 @@ async def test_get_progress_index_unauth(hass, client, hass_admin_user):
@asyncio.coroutine @asyncio.coroutine
def test_get_progress_flow(hass, client): def test_get_progress_flow(hass, client):
"""Test we can query the API for same result as we get from init a flow.""" """Test we can query the API for same result as we get from init a flow."""
mock_entity_platform(hass, 'config_flow.test', None)
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
@asyncio.coroutine @asyncio.coroutine
def async_step_user(self, user_input=None): def async_step_user(self, user_input=None):
@ -474,6 +488,8 @@ def test_get_progress_flow(hass, client):
async def test_get_progress_flow_unauth(hass, client, hass_admin_user): async def test_get_progress_flow_unauth(hass, client, hass_admin_user):
"""Test we can can't query the API for result of flow.""" """Test we can can't query the API for result of flow."""
mock_entity_platform(hass, 'config_flow.test', None)
class TestFlow(core_ce.ConfigFlow): class TestFlow(core_ce.ConfigFlow):
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
schema = OrderedDict() schema = OrderedDict()

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