Define data flow result type (#49260)

* Define data flow result type

* Revert explicit definitions

* Fix tests

* Specific mypy ignore
This commit is contained in:
Ruslan Sayfutdinov 2021-04-15 18:17:07 +01:00 committed by GitHub
parent dafc7a072c
commit 80f66f301b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 169 additions and 114 deletions

View File

@ -4,13 +4,14 @@ from __future__ import annotations
import asyncio
from collections import OrderedDict
from datetime import timedelta
from typing import Any, Dict, Optional, Tuple, cast
from typing import Any, Dict, Mapping, Optional, Tuple, cast
import jwt
from homeassistant import data_entry_flow
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.util import dt as dt_util
from . import auth_store, models
@ -97,8 +98,8 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager):
return await auth_provider.async_login_flow(context)
async def async_finish_flow(
self, flow: data_entry_flow.FlowHandler, result: dict[str, Any]
) -> dict[str, Any]:
self, flow: data_entry_flow.FlowHandler, result: FlowResultDict
) -> FlowResultDict:
"""Return a user as result of login flow."""
flow = cast(LoginFlow, flow)
@ -115,7 +116,7 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager):
raise KeyError(f"Unknown auth provider {result['handler']}")
credentials = await auth_provider.async_get_or_create_credentials(
result["data"]
cast(Mapping[str, str], result["data"]),
)
if flow.context.get("credential_only"):

View File

@ -12,6 +12,7 @@ from voluptuous.humanize import humanize_error
from homeassistant import data_entry_flow, requirements
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.decorator import Registry
@ -105,7 +106,7 @@ class SetupFlow(data_entry_flow.FlowHandler):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the first step of setup flow.
Return self.async_show_form(step_id='init') if user_input is None.

View File

@ -14,6 +14,7 @@ import voluptuous as vol
from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.exceptions import ServiceNotFound
from homeassistant.helpers import config_validation as cv
@ -292,7 +293,7 @@ class NotifySetupFlow(SetupFlow):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Let user select available notify services."""
errors: dict[str, str] = {}
@ -318,7 +319,7 @@ class NotifySetupFlow(SetupFlow):
async def async_step_setup(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Verify user can receive one-time password."""
errors: dict[str, str] = {}

View File

@ -9,6 +9,7 @@ import voluptuous as vol
from homeassistant.auth.models import User
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultDict
from . import (
MULTI_FACTOR_AUTH_MODULE_SCHEMA,
@ -189,7 +190,7 @@ class TotpSetupFlow(SetupFlow):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the first step of setup flow.
Return self.async_show_form(step_id='init') if user_input is None.

View File

@ -1,6 +1,7 @@
"""Auth providers for Home Assistant."""
from __future__ import annotations
from collections.abc import Mapping
import importlib
import logging
import types
@ -12,6 +13,7 @@ from voluptuous.humanize import humanize_error
from homeassistant import data_entry_flow, requirements
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import dt as dt_util
from homeassistant.util.decorator import Registry
@ -102,7 +104,7 @@ class AuthProvider:
raise NotImplementedError
async def async_get_or_create_credentials(
self, flow_result: dict[str, str]
self, flow_result: Mapping[str, str]
) -> Credentials:
"""Get credentials based on the flow result."""
raise NotImplementedError
@ -198,7 +200,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the first step of login flow.
Return self.async_show_form(step_id='init') if user_input is None.
@ -208,7 +210,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
async def async_step_select_mfa_module(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the step of select mfa module."""
errors = {}
@ -233,7 +235,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
async def async_step_mfa(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the step of mfa validation."""
assert self.credential
assert self.user
@ -285,6 +287,6 @@ class LoginFlow(data_entry_flow.FlowHandler):
errors=errors,
)
async def async_finish(self, flow_result: Any) -> dict:
async def async_finish(self, flow_result: Any) -> FlowResultDict:
"""Handle the pass of login flow."""
return self.async_create_entry(title=self._auth_provider.name, data=flow_result)

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio.subprocess
import collections
from collections.abc import Mapping
import logging
import os
from typing import Any, cast
@ -10,6 +11,7 @@ from typing import Any, cast
import voluptuous as vol
from homeassistant.const import CONF_COMMAND
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
@ -100,7 +102,7 @@ class CommandLineAuthProvider(AuthProvider):
self._user_meta[username] = meta
async def async_get_or_create_credentials(
self, flow_result: dict[str, str]
self, flow_result: Mapping[str, str]
) -> Credentials:
"""Get credentials based on the flow result."""
username = flow_result["username"]
@ -127,7 +129,7 @@ class CommandLineLoginFlow(LoginFlow):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the step of the form."""
errors = {}

View File

@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
import base64
from collections import OrderedDict
from collections.abc import Mapping
import logging
from typing import Any, cast
@ -12,6 +13,7 @@ import voluptuous as vol
from homeassistant.const import CONF_ID
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
@ -277,7 +279,7 @@ class HassAuthProvider(AuthProvider):
await self.data.async_save()
async def async_get_or_create_credentials(
self, flow_result: dict[str, str]
self, flow_result: Mapping[str, str]
) -> Credentials:
"""Get credentials based on the flow result."""
if self.data is None:
@ -319,7 +321,7 @@ class HassLoginFlow(LoginFlow):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the step of the form."""
errors = {}

View File

@ -2,12 +2,14 @@
from __future__ import annotations
from collections import OrderedDict
from collections.abc import Mapping
import hmac
from typing import Any, cast
from typing import cast
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
@ -62,7 +64,7 @@ class ExampleAuthProvider(AuthProvider):
raise InvalidAuthError
async def async_get_or_create_credentials(
self, flow_result: dict[str, str]
self, flow_result: Mapping[str, str]
) -> Credentials:
"""Get credentials based on the flow result."""
username = flow_result["username"]
@ -97,7 +99,7 @@ class ExampleLoginFlow(LoginFlow):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the step of the form."""
errors = {}

View File

@ -5,12 +5,14 @@ It will be removed when auth system production ready
"""
from __future__ import annotations
from collections.abc import Mapping
import hmac
from typing import Any, cast
from typing import cast
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
@ -57,7 +59,7 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
raise InvalidAuthError
async def async_get_or_create_credentials(
self, flow_result: dict[str, str]
self, flow_result: Mapping[str, str]
) -> Credentials:
"""Return credentials for this login."""
credentials = await self.async_credentials()
@ -82,7 +84,7 @@ class LegacyLoginFlow(LoginFlow):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the step of the form."""
errors = {}

View File

@ -5,6 +5,7 @@ Abort login flow if not access from trusted network.
"""
from __future__ import annotations
from collections.abc import Mapping
from ipaddress import (
IPv4Address,
IPv4Network,
@ -18,6 +19,7 @@ from typing import Any, Dict, List, Union, cast
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
@ -127,7 +129,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
)
async def async_get_or_create_credentials(
self, flow_result: dict[str, str]
self, flow_result: Mapping[str, str]
) -> Credentials:
"""Get credentials based on the flow result."""
user_id = flow_result["user"]
@ -199,7 +201,7 @@ class TrustedNetworksLoginFlow(LoginFlow):
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the step of the form."""
try:
cast(

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
HTTP_UNAUTHORIZED,
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import DiscoveryInfoType
@ -91,7 +92,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_, hub_name = await _validate_input(self.hass, self._discovered)
self._discovered[CONF_NAME] = hub_name
async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType) -> dict[str, Any]: # type: ignore
async def async_step_zeroconf( # type: ignore[override]
self, discovery_info: DiscoveryInfoType
) -> FlowResultDict:
"""Handle a flow initialized by zeroconf discovery."""
name: str = discovery_info[CONF_NAME]
host: str = discovery_info[CONF_HOST]
@ -115,7 +118,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle confirmation flow for discovered bond hub."""
errors = {}
if user_input is not None:
@ -156,7 +159,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle a flow initialized by the user."""
errors = {}
if user_input is not None:

View File

@ -323,7 +323,7 @@ def get_core_info(hass):
@callback
@bind_hass
def is_hassio(hass):
def is_hassio(hass: HomeAssistant) -> bool:
"""Return true if Hass.io is loaded.
Async friendly.

View File

@ -29,6 +29,8 @@ from homeassistant.const import (
CONF_USERNAME,
)
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.helpers.typing import DiscoveryInfoType
from .const import (
CONNECTION_TIMEOUT,
@ -58,7 +60,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self,
user_input: dict[str, Any] | None = None,
errors: dict[str, str] | None = None,
) -> dict[str, Any]:
) -> FlowResultDict:
if user_input is None:
user_input = {}
return self.async_show_form(
@ -85,7 +87,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle import initiated config flow."""
return await self.async_step_user(user_input)
@ -99,7 +101,7 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle user initiated config flow."""
if user_input is None:
return await self._async_show_user_form()
@ -211,9 +213,9 @@ class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=title, data=user_input)
async def async_step_ssdp( # type: ignore # mypy says signature incompatible with supertype, but it's the same?
self, discovery_info: dict[str, Any]
) -> dict[str, Any]:
async def async_step_ssdp( # type: ignore[override]
self, discovery_info: DiscoveryInfoType
) -> FlowResultDict:
"""Handle SSDP initiated config flow."""
await self.async_set_unique_id(discovery_info[ssdp.ATTR_UPNP_UDN])
self._abort_if_unique_id_configured()
@ -254,7 +256,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle options flow."""
# Recipients are persisted as a list, but handled as comma separated string in UI

View File

@ -27,6 +27,7 @@ from homeassistant.const import (
CONF_TOKEN,
)
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResultDict
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
@ -130,7 +131,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def _advance_to_auth_step_if_necessary(
self, hyperion_client: client.HyperionClient
) -> dict[str, Any]:
) -> FlowResultDict:
"""Determine if auth is required."""
auth_resp = await hyperion_client.async_is_auth_required()
@ -145,7 +146,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_reauth(
self,
config_data: ConfigType,
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle a reauthentication flow."""
self._data = dict(config_data)
async with self._create_client(raw_connection=True) as hyperion_client:
@ -153,9 +154,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="cannot_connect")
return await self._advance_to_auth_step_if_necessary(hyperion_client)
async def async_step_ssdp( # type: ignore[override]
self, discovery_info: dict[str, Any]
) -> dict[str, Any]:
async def async_step_ssdp(self, discovery_info: dict[str, Any]) -> FlowResultDict: # type: ignore[override]
"""Handle a flow initiated by SSDP."""
# Sample data provided by SSDP: {
# 'ssdp_location': 'http://192.168.0.1:8090/description.xml',
@ -226,7 +225,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_user(
self,
user_input: ConfigType | None = None,
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle a flow initiated by the user."""
errors = {}
if user_input:
@ -297,7 +296,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_auth(
self,
user_input: ConfigType | None = None,
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the auth step of a flow."""
errors = {}
if user_input:
@ -326,7 +325,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_create_token(
self, user_input: ConfigType | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Send a request for a new token."""
if user_input is None:
self._auth_id = client.generate_random_auth_id()
@ -352,7 +351,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_create_token_external(
self, auth_resp: ConfigType | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle completion of the request for a new token."""
if auth_resp is not None and client.ResponseOK(auth_resp):
token = auth_resp.get(const.KEY_INFO, {}).get(const.KEY_TOKEN)
@ -365,7 +364,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_create_token_success(
self, _: ConfigType | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Create an entry after successful token creation."""
# Clean-up the request task.
await self._cancel_request_token_task()
@ -381,7 +380,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_create_token_fail(
self, _: ConfigType | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Show an error on the auth form."""
# Clean-up the request task.
await self._cancel_request_token_task()
@ -389,7 +388,7 @@ class HyperionConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_confirm(
self, user_input: ConfigType | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Get final confirmation before entry creation."""
if user_input is None and self._require_confirm:
return self.async_show_form(
@ -449,7 +448,7 @@ class HyperionOptionsFlow(OptionsFlow):
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Manage the options."""
effects = {source: source for source in const.KEY_COMPONENTID_EXTERNAL_SOURCES}

View File

@ -14,7 +14,7 @@ from homeassistant import config_entries, exceptions
from homeassistant.components.hassio import is_hassio
from homeassistant.const import CONF_URL
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.data_entry_flow import AbortFlow, FlowResultDict
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .addon import AddonError, AddonManager, get_addon_manager
@ -89,16 +89,16 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle the initial step."""
if is_hassio(self.hass): # type: ignore # no-untyped-call
if is_hassio(self.hass):
return await self.async_step_on_supervisor()
return await self.async_step_manual()
async def async_step_manual(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle a manual configuration."""
if user_input is None:
return self.async_show_form(
@ -134,9 +134,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
step_id="manual", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_hassio( # type: ignore # override
self, discovery_info: dict[str, Any]
) -> dict[str, Any]:
async def async_step_hassio(self, discovery_info: dict[str, Any]) -> FlowResultDict: # type: ignore[override]
"""Receive configuration from add-on discovery info.
This flow is triggered by the Z-Wave JS add-on.
@ -154,7 +152,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_hassio_confirm(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Confirm the add-on discovery."""
if user_input is not None:
return await self.async_step_on_supervisor(
@ -164,7 +162,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_show_form(step_id="hassio_confirm")
@callback
def _async_create_entry_from_vars(self) -> dict[str, Any]:
def _async_create_entry_from_vars(self) -> FlowResultDict:
"""Return a config entry for the flow."""
return self.async_create_entry(
title=TITLE,
@ -179,7 +177,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_on_supervisor(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle logic when on Supervisor host."""
if user_input is None:
return self.async_show_form(
@ -203,7 +201,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_install_addon(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Install Z-Wave JS add-on."""
if not self.install_task:
self.install_task = self.hass.async_create_task(self._async_install_addon())
@ -223,13 +221,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_install_failed(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Add-on installation failed."""
return self.async_abort(reason="addon_install_failed")
async def async_step_configure_addon(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Ask for config for Z-Wave JS add-on."""
addon_config = await self._async_get_addon_config()
@ -265,7 +263,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_start_addon(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Start Z-Wave JS add-on."""
if not self.start_task:
self.start_task = self.hass.async_create_task(self._async_start_addon())
@ -283,7 +281,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_start_failed(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Add-on start failed."""
return self.async_abort(reason="addon_start_failed")
@ -320,7 +318,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_finish_addon_setup(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Prepare info needed to complete the config entry.
Get add-on discovery info and server version info.

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from collections.abc import Mapping
from contextvars import ContextVar
import functools
import logging
@ -21,7 +22,7 @@ from homeassistant.exceptions import (
)
from homeassistant.helpers import device_registry, entity_registry
from homeassistant.helpers.event import Event
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from homeassistant.helpers.typing import UNDEFINED, DiscoveryInfoType, UndefinedType
from homeassistant.setup import async_process_deps_reqs, async_setup_component
from homeassistant.util.decorator import Registry
import homeassistant.util.uuid as uuid_util
@ -146,7 +147,7 @@ class ConfigEntry:
version: int,
domain: str,
title: str,
data: dict,
data: Mapping[str, Any],
source: str,
connection_class: str,
system_options: dict,
@ -559,8 +560,8 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager):
self._hass_config = hass_config
async def async_finish_flow(
self, flow: data_entry_flow.FlowHandler, result: dict[str, Any]
) -> dict[str, Any]:
self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResultDict
) -> data_entry_flow.FlowResultDict:
"""Finish a config flow and add an entry."""
flow = cast(ConfigFlow, flow)
@ -668,7 +669,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager):
return flow
async def async_post_init(
self, flow: data_entry_flow.FlowHandler, result: dict
self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResultDict
) -> None:
"""After a flow is initialised trigger new flow notifications."""
source = flow.context["source"]
@ -931,7 +932,7 @@ class ConfigEntries:
unique_id: str | dict | None | UndefinedType = UNDEFINED,
title: str | dict | UndefinedType = UNDEFINED,
data: dict | UndefinedType = UNDEFINED,
options: dict | UndefinedType = UNDEFINED,
options: Mapping[str, Any] | UndefinedType = UNDEFINED,
system_options: dict | UndefinedType = UNDEFINED,
) -> bool:
"""Update a config entry.
@ -956,7 +957,7 @@ class ConfigEntries:
changed = True
entry.data = MappingProxyType(data)
if options is not UNDEFINED and entry.options != options: # type: ignore
if options is not UNDEFINED and entry.options != options:
changed = True
entry.options = MappingProxyType(options)
@ -1147,7 +1148,9 @@ class ConfigFlow(data_entry_flow.FlowHandler):
}
@callback
def _async_in_progress(self, include_uninitialized: bool = False) -> list[dict]:
def _async_in_progress(
self, include_uninitialized: bool = False
) -> list[data_entry_flow.FlowResultDict]:
"""Return other in progress flows for current domain."""
return [
flw
@ -1157,18 +1160,22 @@ class ConfigFlow(data_entry_flow.FlowHandler):
if flw["handler"] == self.handler and flw["flow_id"] != self.flow_id
]
async def async_step_ignore(self, user_input: dict[str, Any]) -> dict[str, Any]:
async def async_step_ignore(
self, user_input: dict[str, Any]
) -> data_entry_flow.FlowResultDict:
"""Ignore this config flow."""
await self.async_set_unique_id(user_input["unique_id"], raise_on_progress=False)
return self.async_create_entry(title=user_input["title"], data={})
async def async_step_unignore(self, user_input: dict[str, Any]) -> dict[str, Any]:
async def async_step_unignore(
self, user_input: dict[str, Any]
) -> data_entry_flow.FlowResultDict:
"""Rediscover a config entry by it's unique_id."""
return self.async_abort(reason="not_implemented")
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> data_entry_flow.FlowResultDict:
"""Handle a flow initiated by the user."""
return self.async_abort(reason="not_implemented")
@ -1197,8 +1204,8 @@ class ConfigFlow(data_entry_flow.FlowHandler):
raise data_entry_flow.AbortFlow("already_in_progress")
async def async_step_discovery(
self, discovery_info: dict[str, Any]
) -> dict[str, Any]:
self, discovery_info: DiscoveryInfoType
) -> data_entry_flow.FlowResultDict:
"""Handle a flow initialized by discovery."""
await self._async_handle_discovery_without_unique_id()
return await self.async_step_user()
@ -1206,7 +1213,7 @@ class ConfigFlow(data_entry_flow.FlowHandler):
@callback
def async_abort(
self, *, reason: str, description_placeholders: dict | None = None
) -> dict[str, Any]:
) -> data_entry_flow.FlowResultDict:
"""Abort the config flow."""
# Remove reauth notification if no reauth flows are in progress
if self.source == SOURCE_REAUTH and not any(
@ -1254,8 +1261,8 @@ class OptionsFlowManager(data_entry_flow.FlowManager):
return cast(OptionsFlow, HANDLERS[entry.domain].async_get_options_flow(entry))
async def async_finish_flow(
self, flow: data_entry_flow.FlowHandler, result: dict[str, Any]
) -> dict[str, Any]:
self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResultDict
) -> data_entry_flow.FlowResultDict:
"""Finish an options flow and update options for configuration entry.
Flow.handler and entry_id is the same thing to map flow with entry.

View File

@ -5,7 +5,7 @@ import abc
import asyncio
from collections.abc import Mapping
from types import MappingProxyType
from typing import Any
from typing import Any, TypedDict
import uuid
import voluptuous as vol
@ -51,6 +51,29 @@ class AbortFlow(FlowError):
self.description_placeholders = description_placeholders
class FlowResultDict(TypedDict, total=False):
"""Typed result dict."""
version: int
type: str
flow_id: str
handler: str
title: str
data: Mapping[str, Any]
step_id: str
data_schema: vol.Schema
extra: str
required: bool
errors: dict[str, str] | None
description: str | None
description_placeholders: dict[str, Any] | None
progress_action: str
url: str
reason: str
context: dict[str, Any]
result: Any
class FlowManager(abc.ABC):
"""Manage all the flows that are in progress."""
@ -88,15 +111,17 @@ class FlowManager(abc.ABC):
@abc.abstractmethod
async def async_finish_flow(
self, flow: FlowHandler, result: dict[str, Any]
) -> dict[str, Any]:
self, flow: FlowHandler, result: FlowResultDict
) -> FlowResultDict:
"""Finish a config flow and add an entry."""
async def async_post_init(self, flow: FlowHandler, result: dict[str, Any]) -> None:
async def async_post_init(self, flow: FlowHandler, result: FlowResultDict) -> None:
"""Entry has finished executing its first step asynchronously."""
@callback
def async_progress(self, include_uninitialized: bool = False) -> list[dict]:
def async_progress(
self, include_uninitialized: bool = False
) -> list[FlowResultDict]:
"""Return the flows in progress."""
return [
{
@ -110,8 +135,8 @@ class FlowManager(abc.ABC):
]
async def async_init(
self, handler: str, *, context: dict | None = None, data: Any = None
) -> Any:
self, handler: str, *, context: dict[str, Any] | None = None, data: Any = None
) -> FlowResultDict:
"""Start a configuration flow."""
if context is None:
context = {}
@ -160,7 +185,7 @@ class FlowManager(abc.ABC):
async def async_configure(
self, flow_id: str, user_input: dict | None = None
) -> Any:
) -> FlowResultDict:
"""Continue a configuration flow."""
flow = self._progress.get(flow_id)
@ -217,7 +242,7 @@ class FlowManager(abc.ABC):
step_id: str,
user_input: dict | None,
step_done: asyncio.Future | None = None,
) -> dict:
) -> FlowResultDict:
"""Handle a step of a flow."""
method = f"async_step_{step_id}"
@ -230,7 +255,7 @@ class FlowManager(abc.ABC):
)
try:
result: dict = await getattr(flow, method)(user_input)
result: FlowResultDict = await getattr(flow, method)(user_input)
except AbortFlow as err:
result = _create_abort_data(
flow.flow_id, flow.handler, err.reason, err.description_placeholders
@ -265,7 +290,7 @@ class FlowManager(abc.ABC):
return result
# We pass a copy of the result because we're mutating our version
result = await self.async_finish_flow(flow, dict(result))
result = await self.async_finish_flow(flow, result.copy())
# _async_finish_flow may change result type, check it again
if result["type"] == RESULT_TYPE_FORM:
@ -288,7 +313,7 @@ class FlowHandler:
hass: HomeAssistant = None # type: ignore
handler: str = None # type: ignore
# Ensure the attribute has a subscriptable, but immutable, default value.
context: dict = MappingProxyType({}) # type: ignore
context: dict[str, Any] = MappingProxyType({}) # type: ignore
# Set by _async_create_flow callback
init_step = "init"
@ -318,9 +343,9 @@ class FlowHandler:
*,
step_id: str,
data_schema: vol.Schema = None,
errors: dict | None = None,
description_placeholders: dict | None = None,
) -> dict[str, Any]:
errors: dict[str, str] | None = None,
description_placeholders: dict[str, Any] | None = None,
) -> FlowResultDict:
"""Return the definition of a form to gather user input."""
return {
"type": RESULT_TYPE_FORM,
@ -340,7 +365,7 @@ class FlowHandler:
data: Mapping[str, Any],
description: str | None = None,
description_placeholders: dict | None = None,
) -> dict[str, Any]:
) -> FlowResultDict:
"""Finish config flow and create a config entry."""
return {
"version": self.VERSION,
@ -356,7 +381,7 @@ class FlowHandler:
@callback
def async_abort(
self, *, reason: str, description_placeholders: dict | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Abort the config flow."""
return _create_abort_data(
self.flow_id, self.handler, reason, description_placeholders
@ -365,7 +390,7 @@ class FlowHandler:
@callback
def async_external_step(
self, *, step_id: str, url: str, description_placeholders: dict | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Return the definition of an external step for the user to take."""
return {
"type": RESULT_TYPE_EXTERNAL_STEP,
@ -377,7 +402,7 @@ class FlowHandler:
}
@callback
def async_external_step_done(self, *, next_step_id: str) -> dict[str, Any]:
def async_external_step_done(self, *, next_step_id: str) -> FlowResultDict:
"""Return the definition of an external step for the user to take."""
return {
"type": RESULT_TYPE_EXTERNAL_STEP_DONE,
@ -393,7 +418,7 @@ class FlowHandler:
step_id: str,
progress_action: str,
description_placeholders: dict | None = None,
) -> dict[str, Any]:
) -> FlowResultDict:
"""Show a progress message to the user, without user input allowed."""
return {
"type": RESULT_TYPE_SHOW_PROGRESS,
@ -405,7 +430,7 @@ class FlowHandler:
}
@callback
def async_show_progress_done(self, *, next_step_id: str) -> dict[str, Any]:
def async_show_progress_done(self, *, next_step_id: str) -> FlowResultDict:
"""Mark the progress done."""
return {
"type": RESULT_TYPE_SHOW_PROGRESS_DONE,
@ -421,7 +446,7 @@ def _create_abort_data(
handler: str,
reason: str,
description_placeholders: dict | None = None,
) -> dict[str, Any]:
) -> FlowResultDict:
"""Return the definition of an external step for the user to take."""
return {
"type": RESULT_TYPE_ABORT,

View File

@ -5,6 +5,8 @@ from typing import Any, Awaitable, Callable, Union
from homeassistant import config_entries
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.helpers.typing import DiscoveryInfoType
DiscoveryFunctionType = Callable[[], Union[Awaitable[bool], bool]]
@ -29,7 +31,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
@ -40,7 +42,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow):
async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Confirm setup."""
if user_input is None:
self._set_confirm_only()
@ -69,8 +71,8 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow):
return self.async_create_entry(title=self._title, data={})
async def async_step_discovery(
self, discovery_info: dict[str, Any]
) -> dict[str, Any]:
self, discovery_info: DiscoveryInfoType
) -> FlowResultDict:
"""Handle a flow initialized by discovery."""
if self._async_in_progress() or self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
@ -85,7 +87,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow):
async_step_homekit = async_step_discovery
async_step_dhcp = async_step_discovery
async def async_step_import(self, _: dict[str, Any] | None) -> dict[str, Any]:
async def async_step_import(self, _: dict[str, Any] | None) -> FlowResultDict:
"""Handle a flow initialized by import."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
@ -135,7 +137,7 @@ class WebhookFlowHandler(config_entries.ConfigFlow):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Handle a user initiated set up flow to create a webhook."""
if not self._allow_multiple and self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")

View File

@ -23,6 +23,7 @@ from yarl import URL
from homeassistant import config_entries
from homeassistant.components import http
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResultDict
from homeassistant.helpers.network import NoURLAvailableError
from .aiohttp_client import async_get_clientsession
@ -234,7 +235,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta):
async def async_step_pick_implementation(
self, user_input: dict | None = None
) -> dict:
) -> FlowResultDict:
"""Handle a flow start."""
implementations = await async_get_implementations(self.hass, self.DOMAIN)
@ -265,7 +266,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta):
async def async_step_auth(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Create an entry for auth."""
# Flow has been triggered by external data
if user_input:
@ -291,7 +292,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta):
async def async_step_creation(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
) -> FlowResultDict:
"""Create config entry from external data."""
token = await self.flow_impl.async_resolve_external_data(self.external_data)
# Force int for non-compliant oauth2 providers
@ -308,7 +309,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta):
{"auth_implementation": self.flow_impl.domain, "token": token}
)
async def async_oauth_create_entry(self, data: dict) -> dict:
async def async_oauth_create_entry(self, data: dict) -> FlowResultDict:
"""Create an entry for the flow.
Ok to override if you want to fetch extra info or even add another step.

View File

@ -21,7 +21,9 @@ class _BaseFlowManagerView(HomeAssistantView):
self._flow_mgr = flow_mgr
# pylint: disable=no-self-use
def _prepare_result_json(self, result: dict[str, Any]) -> dict[str, Any]:
def _prepare_result_json(
self, result: data_entry_flow.FlowResultDict
) -> data_entry_flow.FlowResultDict:
"""Convert result to JSON."""
if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
data = result.copy()