Add FlowResultType enum to data entry flow (#72955)

This commit is contained in:
epenet 2022-06-08 07:02:44 +02:00 committed by GitHub
parent 329595bf73
commit f91aa33c5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 79 additions and 53 deletions

View file

@ -103,7 +103,7 @@ class AuthManagerFlowManager(data_entry_flow.FlowManager):
"""Return a user as result of login flow.""" """Return a user as result of login flow."""
flow = cast(LoginFlow, flow) flow = cast(LoginFlow, flow)
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
return result return result
# we got final result # we got final result

View file

@ -64,7 +64,7 @@ class AlmondFlowHandler(
"""Handle authorize step.""" """Handle authorize step."""
result = await super().async_step_auth(user_input) result = await super().async_step_auth(user_input)
if result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP: if result["type"] == data_entry_flow.FlowResultType.EXTERNAL_STEP:
self.host = str(URL(result["url"]).with_path("me")) self.host = str(URL(result["url"]).with_path("me"))
return result return result

View file

@ -52,7 +52,7 @@ flow for details.
Progress the flow. Most flows will be 1 page, but could optionally add extra Progress the flow. Most flows will be 1 page, but could optionally add extra
login challenges, like TFA. Once the flow has finished, the returned step will login challenges, like TFA. Once the flow has finished, the returned step will
have type RESULT_TYPE_CREATE_ENTRY and "result" key will contain an authorization code. have type FlowResultType.CREATE_ENTRY and "result" key will contain an authorization code.
The authorization code associated with an authorized user by default, it will The authorization code associated with an authorized user by default, it will
associate with an credential if "type" set to "link_user" in associate with an credential if "type" set to "link_user" in
"/auth/login_flow" "/auth/login_flow"
@ -123,13 +123,13 @@ class AuthProvidersView(HomeAssistantView):
def _prepare_result_json(result): def _prepare_result_json(result):
"""Convert result to JSON.""" """Convert result to JSON."""
if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
data = result.copy() data = result.copy()
data.pop("result") data.pop("result")
data.pop("data") data.pop("data")
return data return data
if result["type"] != data_entry_flow.RESULT_TYPE_FORM: if result["type"] != data_entry_flow.FlowResultType.FORM:
return result return result
data = result.copy() data = result.copy()
@ -154,11 +154,11 @@ class LoginFlowBaseView(HomeAssistantView):
async def _async_flow_result_to_response(self, request, client_id, result): async def _async_flow_result_to_response(self, request, client_id, result):
"""Convert the flow result to a response.""" """Convert the flow result to a response."""
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
# @log_invalid_auth does not work here since it returns HTTP 200. # @log_invalid_auth does not work here since it returns HTTP 200.
# We need to manually log failed login attempts. # We need to manually log failed login attempts.
if ( if (
result["type"] == data_entry_flow.RESULT_TYPE_FORM result["type"] == data_entry_flow.FlowResultType.FORM
and (errors := result.get("errors")) and (errors := result.get("errors"))
and errors.get("base") and errors.get("base")
in ( in (

View file

@ -129,11 +129,11 @@ def websocket_depose_mfa(
def _prepare_result_json(result): def _prepare_result_json(result):
"""Convert result to JSON.""" """Convert result to JSON."""
if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
data = result.copy() data = result.copy()
return data return data
if result["type"] != data_entry_flow.RESULT_TYPE_FORM: if result["type"] != data_entry_flow.FlowResultType.FORM:
return result return result
data = result.copy() data = result.copy()

View file

@ -143,7 +143,7 @@ class ConfigManagerEntryResourceReloadView(HomeAssistantView):
def _prepare_config_flow_result_json(result, prepare_result_json): def _prepare_config_flow_result_json(result, prepare_result_json):
"""Convert result to JSON.""" """Convert result to JSON."""
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
return prepare_result_json(result) return prepare_result_json(result)
data = result.copy() data = result.copy()

View file

@ -11,7 +11,7 @@ import time
from homeassistant.const import CONF_DEVICE, CONF_PLATFORM from homeassistant.const import CONF_DEVICE, CONF_PLATFORM
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import RESULT_TYPE_ABORT from homeassistant.data_entry_flow import FlowResultType
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_connect,
@ -305,7 +305,7 @@ async def async_start( # noqa: C901
) )
if ( if (
result result
and result["type"] == RESULT_TYPE_ABORT and result["type"] == FlowResultType.ABORT
and result["reason"] and result["reason"]
in ("already_configured", "single_instance_allowed") in ("already_configured", "single_instance_allowed")
): ):

View file

@ -682,7 +682,7 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager):
if not self._async_has_other_discovery_flows(flow.flow_id): if not self._async_has_other_discovery_flows(flow.flow_id):
persistent_notification.async_dismiss(self.hass, DISCOVERY_NOTIFICATION_ID) persistent_notification.async_dismiss(self.hass, DISCOVERY_NOTIFICATION_ID)
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
return result return result
# Check if config entry exists with unique ID. Unload it. # Check if config entry exists with unique ID. Unload it.
@ -1534,7 +1534,7 @@ class OptionsFlowManager(data_entry_flow.FlowManager):
""" """
flow = cast(OptionsFlow, flow) flow = cast(OptionsFlow, flow)
if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
return result return result
entry = self.hass.config_entries.async_get_entry(flow.handler) entry = self.hass.config_entries.async_get_entry(flow.handler)

View file

@ -10,10 +10,27 @@ from typing import Any, TypedDict
import voluptuous as vol import voluptuous as vol
from .backports.enum import StrEnum
from .core import HomeAssistant, callback from .core import HomeAssistant, callback
from .exceptions import HomeAssistantError from .exceptions import HomeAssistantError
from .helpers.frame import report
from .util import uuid as uuid_util from .util import uuid as uuid_util
class FlowResultType(StrEnum):
"""Result type for a data entry flow."""
FORM = "form"
CREATE_ENTRY = "create_entry"
ABORT = "abort"
EXTERNAL_STEP = "external"
EXTERNAL_STEP_DONE = "external_done"
SHOW_PROGRESS = "progress"
SHOW_PROGRESS_DONE = "progress_done"
MENU = "menu"
# RESULT_TYPE_* is deprecated, to be removed in 2022.9
RESULT_TYPE_FORM = "form" RESULT_TYPE_FORM = "form"
RESULT_TYPE_CREATE_ENTRY = "create_entry" RESULT_TYPE_CREATE_ENTRY = "create_entry"
RESULT_TYPE_ABORT = "abort" RESULT_TYPE_ABORT = "abort"
@ -64,7 +81,7 @@ class FlowResult(TypedDict, total=False):
"""Typed result dict.""" """Typed result dict."""
version: int version: int
type: str type: FlowResultType
flow_id: str flow_id: str
handler: str handler: str
title: str title: str
@ -207,7 +224,7 @@ class FlowManager(abc.ABC):
self._initialize_tasks[handler].remove(task) self._initialize_tasks[handler].remove(task)
self._initializing[handler].remove(init_done) self._initializing[handler].remove(init_done)
if result["type"] != RESULT_TYPE_ABORT: if result["type"] != FlowResultType.ABORT:
await self.async_post_init(flow, result) await self.async_post_init(flow, result)
return result return result
@ -252,7 +269,7 @@ class FlowManager(abc.ABC):
user_input = cur_step["data_schema"](user_input) user_input = cur_step["data_schema"](user_input)
# Handle a menu navigation choice # Handle a menu navigation choice
if cur_step["type"] == RESULT_TYPE_MENU and user_input: if cur_step["type"] == FlowResultType.MENU and user_input:
result = await self._async_handle_step( result = await self._async_handle_step(
flow, user_input["next_step_id"], None flow, user_input["next_step_id"], None
) )
@ -261,18 +278,25 @@ class FlowManager(abc.ABC):
flow, cur_step["step_id"], user_input flow, cur_step["step_id"], user_input
) )
if cur_step["type"] in (RESULT_TYPE_EXTERNAL_STEP, RESULT_TYPE_SHOW_PROGRESS): if cur_step["type"] in (
if cur_step["type"] == RESULT_TYPE_EXTERNAL_STEP and result["type"] not in ( FlowResultType.EXTERNAL_STEP,
RESULT_TYPE_EXTERNAL_STEP, FlowResultType.SHOW_PROGRESS,
RESULT_TYPE_EXTERNAL_STEP_DONE, ):
if cur_step["type"] == FlowResultType.EXTERNAL_STEP and result[
"type"
] not in (
FlowResultType.EXTERNAL_STEP,
FlowResultType.EXTERNAL_STEP_DONE,
): ):
raise ValueError( raise ValueError(
"External step can only transition to " "External step can only transition to "
"external step or external step done." "external step or external step done."
) )
if cur_step["type"] == RESULT_TYPE_SHOW_PROGRESS and result["type"] not in ( if cur_step["type"] == FlowResultType.SHOW_PROGRESS and result[
RESULT_TYPE_SHOW_PROGRESS, "type"
RESULT_TYPE_SHOW_PROGRESS_DONE, ] not in (
FlowResultType.SHOW_PROGRESS,
FlowResultType.SHOW_PROGRESS_DONE,
): ):
raise ValueError( raise ValueError(
"Show progress can only transition to show progress or show progress done." "Show progress can only transition to show progress or show progress done."
@ -282,7 +306,7 @@ class FlowManager(abc.ABC):
# the frontend. # the frontend.
if ( if (
cur_step["step_id"] != result.get("step_id") cur_step["step_id"] != result.get("step_id")
or result["type"] == RESULT_TYPE_SHOW_PROGRESS or result["type"] == FlowResultType.SHOW_PROGRESS
): ):
# Tell frontend to reload the flow state. # Tell frontend to reload the flow state.
self.hass.bus.async_fire( self.hass.bus.async_fire(
@ -345,25 +369,21 @@ class FlowManager(abc.ABC):
if step_done: if step_done:
step_done.set_result(None) step_done.set_result(None)
if result["type"] not in ( if not isinstance(result["type"], FlowResultType):
RESULT_TYPE_FORM, result["type"] = FlowResultType(result["type"]) # type: ignore[unreachable]
RESULT_TYPE_EXTERNAL_STEP, report(
RESULT_TYPE_CREATE_ENTRY, "does not use FlowResultType enum for data entry flow result type. "
RESULT_TYPE_ABORT, "This is deprecated and will stop working in Home Assistant 2022.9",
RESULT_TYPE_EXTERNAL_STEP_DONE, error_if_core=False,
RESULT_TYPE_SHOW_PROGRESS, )
RESULT_TYPE_SHOW_PROGRESS_DONE,
RESULT_TYPE_MENU,
):
raise ValueError(f"Handler returned incorrect type: {result['type']}")
if result["type"] in ( if result["type"] in (
RESULT_TYPE_FORM, FlowResultType.FORM,
RESULT_TYPE_EXTERNAL_STEP, FlowResultType.EXTERNAL_STEP,
RESULT_TYPE_EXTERNAL_STEP_DONE, FlowResultType.EXTERNAL_STEP_DONE,
RESULT_TYPE_SHOW_PROGRESS, FlowResultType.SHOW_PROGRESS,
RESULT_TYPE_SHOW_PROGRESS_DONE, FlowResultType.SHOW_PROGRESS_DONE,
RESULT_TYPE_MENU, FlowResultType.MENU,
): ):
flow.cur_step = result flow.cur_step = result
return result return result
@ -372,7 +392,7 @@ class FlowManager(abc.ABC):
result = await self.async_finish_flow(flow, result.copy()) result = await self.async_finish_flow(flow, result.copy())
# _async_finish_flow may change result type, check it again # _async_finish_flow may change result type, check it again
if result["type"] == RESULT_TYPE_FORM: if result["type"] == FlowResultType.FORM:
flow.cur_step = result flow.cur_step = result
return result return result
@ -427,7 +447,7 @@ class FlowHandler:
) -> FlowResult: ) -> FlowResult:
"""Return the definition of a form to gather user input.""" """Return the definition of a form to gather user input."""
return { return {
"type": RESULT_TYPE_FORM, "type": FlowResultType.FORM,
"flow_id": self.flow_id, "flow_id": self.flow_id,
"handler": self.handler, "handler": self.handler,
"step_id": step_id, "step_id": step_id,
@ -449,7 +469,7 @@ class FlowHandler:
"""Finish config flow and create a config entry.""" """Finish config flow and create a config entry."""
return { return {
"version": self.VERSION, "version": self.VERSION,
"type": RESULT_TYPE_CREATE_ENTRY, "type": FlowResultType.CREATE_ENTRY,
"flow_id": self.flow_id, "flow_id": self.flow_id,
"handler": self.handler, "handler": self.handler,
"title": title, "title": title,
@ -480,7 +500,7 @@ class FlowHandler:
) -> FlowResult: ) -> FlowResult:
"""Return the definition of an external step for the user to take.""" """Return the definition of an external step for the user to take."""
return { return {
"type": RESULT_TYPE_EXTERNAL_STEP, "type": FlowResultType.EXTERNAL_STEP,
"flow_id": self.flow_id, "flow_id": self.flow_id,
"handler": self.handler, "handler": self.handler,
"step_id": step_id, "step_id": step_id,
@ -492,7 +512,7 @@ class FlowHandler:
def async_external_step_done(self, *, next_step_id: str) -> FlowResult: def async_external_step_done(self, *, next_step_id: str) -> FlowResult:
"""Return the definition of an external step for the user to take.""" """Return the definition of an external step for the user to take."""
return { return {
"type": RESULT_TYPE_EXTERNAL_STEP_DONE, "type": FlowResultType.EXTERNAL_STEP_DONE,
"flow_id": self.flow_id, "flow_id": self.flow_id,
"handler": self.handler, "handler": self.handler,
"step_id": next_step_id, "step_id": next_step_id,
@ -508,7 +528,7 @@ class FlowHandler:
) -> FlowResult: ) -> FlowResult:
"""Show a progress message to the user, without user input allowed.""" """Show a progress message to the user, without user input allowed."""
return { return {
"type": RESULT_TYPE_SHOW_PROGRESS, "type": FlowResultType.SHOW_PROGRESS,
"flow_id": self.flow_id, "flow_id": self.flow_id,
"handler": self.handler, "handler": self.handler,
"step_id": step_id, "step_id": step_id,
@ -520,7 +540,7 @@ class FlowHandler:
def async_show_progress_done(self, *, next_step_id: str) -> FlowResult: def async_show_progress_done(self, *, next_step_id: str) -> FlowResult:
"""Mark the progress done.""" """Mark the progress done."""
return { return {
"type": RESULT_TYPE_SHOW_PROGRESS_DONE, "type": FlowResultType.SHOW_PROGRESS_DONE,
"flow_id": self.flow_id, "flow_id": self.flow_id,
"handler": self.handler, "handler": self.handler,
"step_id": next_step_id, "step_id": next_step_id,
@ -539,7 +559,7 @@ class FlowHandler:
Options dict maps step_id => i18n label Options dict maps step_id => i18n label
""" """
return { return {
"type": RESULT_TYPE_MENU, "type": FlowResultType.MENU,
"flow_id": self.flow_id, "flow_id": self.flow_id,
"handler": self.handler, "handler": self.handler,
"step_id": step_id, "step_id": step_id,
@ -558,7 +578,7 @@ def _create_abort_data(
) -> FlowResult: ) -> FlowResult:
"""Return the definition of an external step for the user to take.""" """Return the definition of an external step for the user to take."""
return { return {
"type": RESULT_TYPE_ABORT, "type": FlowResultType.ABORT,
"flow_id": flow_id, "flow_id": flow_id,
"handler": handler, "handler": handler,
"reason": reason, "reason": reason,

View file

@ -26,7 +26,7 @@ class _BaseFlowManagerView(HomeAssistantView):
self, result: data_entry_flow.FlowResult self, result: data_entry_flow.FlowResult
) -> data_entry_flow.FlowResult: ) -> data_entry_flow.FlowResult:
"""Convert result to JSON.""" """Convert result to JSON."""
if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: if result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY:
data = result.copy() data = result.copy()
data.pop("result") data.pop("result")
data.pop("data") data.pop("data")

View file

@ -42,7 +42,7 @@ class SchemaFlowFormStep:
# The next_step function is called if the schema validates successfully or if no # The next_step function is called if the schema validates successfully or if no
# schema is defined. The next_step function is passed the union of config entry # schema is defined. The next_step function is passed the union of config entry
# options and user input from previous steps. # options and user input from previous steps.
# If next_step returns None, the flow is ended with RESULT_TYPE_CREATE_ENTRY. # If next_step returns None, the flow is ended with FlowResultType.CREATE_ENTRY.
next_step: Callable[[dict[str, Any]], str | None] = lambda _: None next_step: Callable[[dict[str, Any]], str | None] = lambda _: None
# Optional function to allow amending a form schema. # Optional function to allow amending a form schema.

View file

@ -220,6 +220,12 @@ _OBSOLETE_IMPORT: dict[str, list[ObsoleteImportMatch]] = {
constant=re.compile(r"^SOURCE_(\w*)$"), constant=re.compile(r"^SOURCE_(\w*)$"),
), ),
], ],
"homeassistant.data_entry_flow": [
ObsoleteImportMatch(
reason="replaced by FlowResultType enum",
constant=re.compile(r"^RESULT_TYPE_(\w*)$"),
),
],
"homeassistant.helpers.device_registry": [ "homeassistant.helpers.device_registry": [
ObsoleteImportMatch( ObsoleteImportMatch(
reason="replaced by DeviceEntryDisabler enum", reason="replaced by DeviceEntryDisabler enum",