def assert_configs_are_equivalent_besides_ids(
    config1: BlocksConfigDict, config2: BlocksConfigDict, root_keys: tuple = ("mode",)
):
    """Allows you to test if two different Blocks configs produce the same demo.

    Parameters:
    config1 (dict): nested dict with config from the first Blocks instance
    config2 (dict): nested dict with config from the second Blocks instance
    root_keys (Tuple): an interable consisting of which keys to test for equivalence at
        the root level of the config. By default, only "mode" is tested,
        so keys like "version" are ignored.
    """
    config1 = copy.deepcopy(config1)
    config2 = copy.deepcopy(config2)
    config1 = json.loads(json.dumps(config1))  # convert tuples to lists
    config2 = json.loads(json.dumps(config2))

    for key in root_keys:
        if config1[key] != config2[key]:
            raise ValueError(f"Configs have different: {key}")

    if len(config1["components"]) != len(config2["components"]):
        raise ValueError("# of components are different")

    def assert_same_components(config1_id, config2_id):
        c1 = list(filter(lambda c: c["id"] == config1_id, config1["components"]))
        if len(c1) == 0:
            raise ValueError(f"Could not find component with id {config1_id}")
        c1 = c1[0]
        c2 = list(filter(lambda c: c["id"] == config2_id, config2["components"]))
        if len(c2) == 0:
            raise ValueError(f"Could not find component with id {config2_id}")
        c2 = c2[0]
        c1 = copy.deepcopy(c1)
        c1.pop("id")
        c2 = copy.deepcopy(c2)
        c2.pop("id")
        if c1 != c2:
            raise ValueError(f"{c1} does not match {c2}")

    def same_children_recursive(children1, chidren2):
        for child1, child2 in zip(children1, chidren2, strict=False):
            assert_same_components(child1["id"], child2["id"])
            if "children" in child1 or "children" in child2:
                same_children_recursive(child1["children"], child2["children"])

    if "layout" in config1:
        if "layout" not in config2:
            raise ValueError(
                "The first config has a layout key, but the second does not"
            )
        children1 = config1["layout"].get("children", [])
        children2 = config2["layout"].get("children", [])
        same_children_recursive(children1, children2)

    if "dependencies" in config1:
        if "dependencies" not in config2:
            raise ValueError(
                "The first config has a dependencies key, but the second does not"
            )
        for d1, d2 in zip(
            config1["dependencies"], config2["dependencies"], strict=False
        ):
            for t1, t2 in zip(d1.pop("targets"), d2.pop("targets"), strict=False):
                assert_same_components(t1[0], t2[0])
            for i1, i2 in zip(d1.pop("inputs"), d2.pop("inputs"), strict=False):
                assert_same_components(i1, i2)
            for o1, o2 in zip(d1.pop("outputs"), d2.pop("outputs"), strict=False):
                assert_same_components(o1, o2)
            if d1 != d2:
                raise ValueError(f"{d1} does not match {d2}")

    return True
Allows you to test if two different Blocks configs produce the same demo.

    Parameters:
    config1 (dict): nested dict with config from the first Blocks instance
    config2 (dict): nested dict with config from the second Blocks instance
    root_keys (Tuple): an interable consisting of which keys to test for equivalence at
        the root level of the config. By default, only "mode" is tested,
        so keys like "version" are ignored.
    
gradio/utils.py