ConfigFlowHandler and OptionsFlowHandler - managing the same parameter

I have a simple use case. In the configuration of the integration, there are some parameters that I want the user to configure - for example “scan rate” - that I also want to allow them to change later. To allow parameters to be changed later you implement an Options flow

So in both the ConfigFlow and OptionsFlow I have this parameter and hence it gets presented to them both during the initial configuration and later on - when pressing “Configure” on the existing integration. This is good.

However, the OptionsFlow stores the configuration under the “options” key in core.config.entries; while the ConfigFlow stores it under the “data” key. So I end up with two sets of config values for scan rate (data[“scan_rate”] and options[“scan_rate”]. While I could sort through this when the integration loads, it does not seem ideal.

Anyone solve this already? Examples?

An option would be to have my configflow chain into the optionsflow during initial configuration? Is this possible?

That seems like a common problem.

You are right about options vs config flow. Maybe you can keep data duplicated across config and options, have priority on options over config.

Or, you can update config entry using this: kia_uvo/custom_components/kia_uvo at 62ee535ae871bb044163d09ec0562e53db9c51f5 · fuatakgun/kia_uvo · GitHubinit.py#L190

Thank you that is helpful. It looks like I should be able to update the config using that API - I’ll give that a try and let you know how it goes.

1 Like

I was able to get this working. Thanks for your recommendations and the code sample. Here’s the formula:

Step 1 - in the OptionsFlowHandler load the schema defaults from your config_entry.data. This then populates the Options form with the config data

            return self.async_show_form(
                step_id="init",
                data_schema=vol.Schema(
                    {
                        vol.Optional(
                            CONF_APP_ID, default=self.config_entry.data[CONF_APP_ID]
                        ): cv.string,
                        vol.Optional(
                            CONF_CREATE_SENSORS,
                            default=self.config_entry.data[CONF_CREATE_SENSORS],
                        ): cv.boolean,

Step 2: On save of the form. Store it as config data, and store no options

        if user_input is not None:
            if CONF_HOST in self.config_entry.data:
                user_input[CONF_HOST] = self.config_entry.data[CONF_HOST]
            if CONF_EMAIL in self.config_entry.data:
                user_input[CONF_EMAIL] = self.config_entry.data[CONF_EMAIL]
            self.hass.config_entries.async_update_entry(
                self.config_entry, data=user_input, options=self.config_entry.options
            )
            return self.async_create_entry(title="", data={})

A couple of notes. First, in my case the Options being configured are a subset of the config data; hence the first step is to copy the other config data in (in this example email and host) - otherwise it will be erased. Second, by returning data={} no data will get written into options (or more likely options gets set to {}), this way there is only one set of data in config.

4 Likes

I have run into the same issue and being a noob Python programmer it’s driving me nuts. But I do want to understand and well I don’t find it easy to use (building the config flow).

From what I understand is that the configflow runs when you add the integration and the optionsflow when you want to change the configuration from the added integration. The optionsflow stores the options in options and the configflow in data. Does someone have a simple example on how to either use only the options or the data part?
image

Look here:

The configflow, imo, is poorly documented and very opaque. That said it does work.

1 Like

Thanks for you swift reply, your repository is allready way to complex to use. I hope to find a simpler configflow this way. I agree on the way the documentation is written, it should be kept simple without extra’s, just the basics.

It is a complex application. Either way, the config flow presents the parameters in the initial integration configuration AND allows a subset of these to be edited in Options. They all then get saved back into the configuration section with options remaining empty. You will see the key elements where the Options config flow gets loaded from Config and saved back into config.

1 Like

I see, just copy the options to data and on return empty the options.

self.hass.config_entries.async_update_entry(
                self.config_entry, data=user_input, options=self.config_entry.options
            )

Thanks for this solution!

1 Like

Yep, that’s the magic!

Thanks, that solved a two day debug mystery for me. I wish the Config flow/Options flow documentation actually showed them to be two completely different aspects of component config and the examples doesn’t really make it clear either.

1 Like

Pete, you almost saved me from more hours of troubleshooting, I was getting crazy because it was exactly what I needed to implement in my component, I didn’t want to duplicate data from config to options, I wanted to be able to let users edit a subset of config data through the Options Flow.

Now the good news is that I’m able to edit data and saving it in config correctly, the bad news is that the options form is not picking up the labels I configured in en.json, and I can’t understand why. :frowning:

This is the main config form of a first configuration:

This is the options form when I click configure (data is saved correctly, but I don’t see any labels):

image

Any help is greatly appreciated, you can find the source of config_flow.py and en.json in my repo.

Thanks a lot.

It may be that the HASS language is not set to en in your dev environment. Check the .storage/core.config file.

I also have a strings.json in the same directory as the code. I believe when no language is specified it uses those. GitHub - PeteRager/lennoxs30: Home Assistant Lennox S30 / E30 / M30 integration

You can also try copying the .en file to strings.json and see if that fixes it.

But for the config labels it works…I simply duplicated the config lines to options section in the en.json file.

Anyway, I checked core.config: "language": "en"

I also copied en.json to strings.json in source dir, but no luck. :frowning:

Any other idea? I bet it’s some stupid thing in the en.json file…but I can’t see it…it looks good to me.

This morning I restarted HA and it seems to be working fine now, who knows why it didn’t pick-up the labels yesterday. Anyway, seems to be ok now. Thanks a lot @PeteRage, your findings should be in the documentation. It was so hard understanding how to be able to edit a proper working Config Flow, but I guess it’s because I’m not a developer. :slight_smile:

Always thankful for this great community.

image

@PeteRage I forgot to ask one thing: when I save the config, in order to have the integration pick-up the new values, I need to reload it. Is there a way to reload it programmatically when the user clicks submit?

There may be, but i don’t know how to do it.

Thanks anyway Pete.

Reloading it @alexdelprete

1 Like

thank you so much.

questions:

  1. Since I have to reload in the Option Flow, where in reality I’m editing config entries, can I use that function also there or it only works in the context of Config Flow?
  2. I found another example that has a structure more similar to my component, with the difference that it’s used in ConfigFlow. So the idea is to put these two in my option flow, after I update the config items in async_step_init. Do you think it’s ok?
class ABBPowerOnePVISunSpecOptionsFlow(config_entries.OptionsFlow):
    """Config flow options handler"""

    VERSION = 1

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize option flow instance"""
        self.config_entry = config_entry
        self.data_schema=vol.Schema(
            {
                vol.Required(
                    CONF_PORT,
                    default=self.config_entry.data[CONF_PORT],
                ): vol.Coerce(int),
                vol.Required(
                    CONF_SLAVE_ID,
                    default=self.config_entry.data[CONF_SLAVE_ID],
                ): vol.Coerce(int),
                vol.Required(
                    CONF_BASE_ADDR,
                    default=self.config_entry.data[CONF_BASE_ADDR],
                ): vol.Coerce(int),
                vol.Required(
                    CONF_SCAN_INTERVAL,
                    default=self.config_entry.data[CONF_SCAN_INTERVAL],
                ): vol.All(vol.Coerce(int), vol.Range(min=5, max=900)),
            }
        )
    async def async_step_init(self, user_input=None):  # pylint: disable=unused-argument
        """Manage the options"""

        if user_input is not None:
            # complete non-edited entries before update (ht @PeteRage)
            if CONF_NAME in self.config_entry.data:
                user_input[CONF_NAME] = self.config_entry.data[CONF_NAME]
            if CONF_HOST in self.config_entry.data:
                user_input[CONF_HOST] = self.config_entry.data[CONF_HOST]

            # write updated config entries (ht @PeteRage / @anon63427907)
            self.hass.config_entries.async_update_entry(
                self.config_entry, data=user_input, options=self.config_entry.options
            )
            # reload updated config entries (ht @anon63427907)
            await self.hass.config_entries.async_reload(self.config_entry.entry_id)
            self.async_abort(reason="configuration updated")

            # write empty options entries (ht @PeteRage / @anon63427907)
            return self.async_create_entry(title="", data={})

        return self.async_show_form(step_id="init", data_schema=self.data_schema)