Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 50 additions & 26 deletions gradio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,39 +548,51 @@ def assert_configs_are_equivalent_besides_ids(
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))

# Preprocess component lists into id->component lookup dicts ONCE for O(1) lookup
def build_component_dict(components: list[dict]) -> dict:
return {c["id"]: c for c in components}

# Defensive checks, leave unchanged.
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")

comp1_by_id = build_component_dict(config1["components"])
comp2_by_id = build_component_dict(config2["components"])

def assert_same_components(config1_id, config2_id):
c1 = list(filter(lambda c: c["id"] == config1_id, config1["components"]))
if len(c1) == 0:
# O(1) lookup now, much faster for large component lists
try:
c1 = comp1_by_id[config1_id]
except KeyError:
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:
try:
c2 = comp2_by_id[config2_id]
except KeyError:
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):

# Only deep copy the specific component, not whole config
c1_prime = c1.copy()
c1_prime.pop("id")
c2_prime = c2.copy()
c2_prime.pop("id")
if c1_prime != c2_prime:
raise ValueError(f"{c1_prime} does not match {c2_prime}")

def same_children_recursive(children1, children2):
# Zip using min(len(children1), len(children2)) to replicate strict=False behavior safely
for child1, child2 in zip(children1, children2):
assert_same_components(child1["id"], child2["id"])
if "children" in child1 or "children" in child2:
same_children_recursive(child1["children"], child2["children"])
same_children_recursive(
child1.get("children", []), child2.get("children", [])
)

if "layout" in config1:
if "layout" not in config2:
Expand All @@ -596,17 +608,29 @@ def same_children_recursive(children1, chidren2):
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):
for d1, d2 in zip(config1["dependencies"], config2["dependencies"]):
# Instead of .pop (which mutates the dict) and forces deepcopy,
# clone the dict once and remove keys, to avoid mutating outer data
d1_clone = d1.copy()
d2_clone = d2.copy()

targets1 = d1_clone.pop("targets")
targets2 = d2_clone.pop("targets")
for t1, t2 in zip(targets1, targets2):
assert_same_components(t1[0], t2[0])
for i1, i2 in zip(d1.pop("inputs"), d2.pop("inputs"), strict=False):

inputs1 = d1_clone.pop("inputs")
inputs2 = d2_clone.pop("inputs")
for i1, i2 in zip(inputs1, inputs2):
assert_same_components(i1, i2)
for o1, o2 in zip(d1.pop("outputs"), d2.pop("outputs"), strict=False):

outputs1 = d1_clone.pop("outputs")
outputs2 = d2_clone.pop("outputs")
for o1, o2 in zip(outputs1, outputs2):
assert_same_components(o1, o2)
if d1 != d2:
raise ValueError(f"{d1} does not match {d2}")

if d1_clone != d2_clone:
raise ValueError(f"{d1_clone} does not match {d2_clone}")

return True

Expand Down