Repository: streamlit/streamlit
File: lib/streamlit/web/bootstrap.py (Python)

Code before:
    # We want to filter out two things: values that are None, and values that
    # are empty tuples. The latter is a special case that indicates that the
    # no values were provided, and the config should reset to the default
    options_from_flags = {
        name.replace("_", "."): val
        for name, val in flag_options.items()
        if val is not None and val != ()
    }

    # Force a reparse of config files (if they exist). The result is cached
    # for future calls.
    config.get_config_options(force_reparse=True, options_from_flags=options_from_flags)


def _install_config_watchers(flag_options: dict[str, Any]) -> None:
    def on_config_changed(_path: str) -> None:
        load_config_options(flag_options)

    for filename in config.get_config_files("config.toml"):
        if os.path.exists(filename):
            # Watch existing file for modifications
            watch_file(filename, on_config_changed)
        else:
            # Watch parent directory for file creation
            parent_dir = os.path.dirname(filename)
            watch_dir(
                parent_dir,
                on_config_changed,
                glob_pattern="config.toml",
                allow_nonexistent=True,
            )


def run_asgi_app(
    main_script_path: str,
    app_import_string: str,
    args: list[str],
    flag_options: dict[str, Any],
) -> None:
    """Run a Streamlit st.App with uvicorn.

    This function is called when `streamlit run` detects an st.App instance
    in the script. It sets up the process environment and starts uvicorn.

    The App instance handles its own Streamlit-specific setup via its lifespan
    (e.g., loading secrets, preparing runtime), while this function handles
    the process-level setup that the CLI is responsible for.

    Parameters
    ----------
    main_script_path

Review comment (security):
**question:** Will there be any unintended side-effects of this change? If I'm reading this correct, changing `secrets.toml` will now also trigger a re-run in this conditional branch, but not if `config.toml` already exists. That seems like unexpected behavior to me.

Code after:
    # We want to filter out two things: values that are None, and values that
    # are empty tuples. The latter is a special case that indicates that the
    # no values were provided, and the config should reset to the default
    options_from_flags = {
        name.replace("_", "."): val
        for name, val in flag_options.items()
        if val is not None and val != ()
    }

    # Force a reparse of config files (if they exist). The result is cached
    # for future calls.
    config.get_config_options(force_reparse=True, options_from_flags=options_from_flags)


def _install_config_watchers(flag_options: dict[str, Any]) -> None:
    def on_config_changed(_path: str) -> None:
        load_config_options(flag_options)

    for filename in config.get_config_files("config.toml"):
        # Watch each config file path directly, even if it doesn't exist yet.
        # This allows detecting both file creation and subsequent modifications.
        # We use the poll watcher because:
        # 1. It handles non-existent paths gracefully, including when parent
        #    directories (like ~/.streamlit/) don't exist yet. The event-based
        #    watcher requires the parent directory to exist to schedule a watch.
        # 2. Config files change rarely, so the polling overhead is negligible.
        # 3. The 200ms poll interval latency is imperceptible for config reloads.
        watch_file(
            filename, on_config_changed, watcher_type="poll", allow_nonexistent=True
        )


def run_asgi_app(
    main_script_path: str,
    app_import_string: str,
    args: list[str],
    flag_options: dict[str, Any],
) -> None:
    """Run a Streamlit st.App with uvicorn.

    This function is called when `streamlit run` detects an st.App instance
    in the script. It sets up the process environment and starts uvicorn.

    The App instance handles its own Streamlit-specific setup via its lifespan
    (e.g., loading secrets, preparing runtime), while this function handles
    the process-level setup that the CLI is responsible for.

    Parameters
    ----------
    main_script_path
        Path to the main Streamlit script containing the st.App.