pathwaycom/pathway · error · ValueError

iterated table not marked as 'iterate_universe' changed its

Error message

iterated table not marked as 'iterate_universe' changed its universe

What it means

In iterative transformers, an output table that was NOT marked with pw.iterate_universe must keep the same universe (set of row ids) as its corresponding input table between iteration steps. After calling the iteration logic, Pathway checks table._universe == input_copy[name]._universe; if a filter/join inside the step changed the row set, this ValueError fires because Pathway cannot reconcile the changed universe without the iterate_universe marker.

Source

Thrown at python/pathway/internals/operator.py:416

            iterated_with_universe_copy
        )
        self.extra_copy = input_copy.subtract_keys(self.iterated_copy)

        # prepare iteration result
        self.result_iterated_with_universe = result.intersect_keys(
            iterated_with_universe_copy
        )
        self.result_iterated = result.subtract_keys(iterated_with_universe_copy)

        # materialize output
        output = type(arg_tuple).empty()
        for name, table in result.items():
            if name in self.iterated_with_universe_copy:
                universe = Universe()
            elif table._universe == input_copy[name]._universe:
                universe = input[name]._universe
            else:
                raise ValueError(
                    "iterated table not marked as 'iterate_universe' changed its universe"
                )
            output[name] = table._materialize(universe)
        output = output.with_same_order(
            self.result_iterated + self.result_iterated_with_universe
        )

        self._prepare_inputs(input)
        self._prepare_outputs(output)
        return output.to_output()

    def _copy_input_table(self, name: str, table: tables.Table, unique: bool):
        if unique:
            universe = Universe()
        else:
            universe = self._universe_mapping[table._universe]
        table_copy = table._materialize(universe)
        table_copy._set_source(OutputHandle(self, name, table_copy))

View on GitHub (pinned to fa2f74a464)

Solutions

  1. Wrap the argument whose universe changes with pw.iterate_universe(table) at the pw.iterate call site — that is exactly what the marker is for.
  2. Or remove the universe-changing operation (filter/join/flatten) from the step so every plain table keeps its row set across steps.
  3. If filtering is needed only for the final result, apply it after pw.iterate returns, not inside the step.

Example fix

# before
result = pw.iterate(step, edges=edges, frontier=frontier)  # step filters frontier

# after
result = pw.iterate(step, edges=edges, frontier=pw.iterate_universe(frontier))
Defensive patterns

Strategy: validation

Validate before calling

import pathway as pw

def plain_args_keep_universe(inputs: dict, outputs: dict) -> bool:
    return all(
        outputs[name]._universe == inputs[name]._universe
        for name in inputs
        if name in outputs
    )

Prevention

When it happens

Trigger: A @pw.table_transformer iteration step that applies .filter(), .flatten(), a join that drops rows, or .with_id() on an argument passed as a plain pw.Table (not wrapped in pw.iterate_universe), while reusing the same argument name in the returned dict.

Common situations: Writing iterative graph algorithms where each step prunes finished nodes; forgetting to mark the shrinking table with pw.iterate_universe when calling pw.iterate.

Related errors


AI-assisted analysis of pathwaycom/pathway@fa2f74a464 (2026-08-15). Data as JSON: /api/errors/c232ff1c89c8ef8f. Report an issue: GitHub.