pathwaycom/pathway · error · ValueError

Trying to delete a row in {type(self)} but deletions_enabled

Error message

Trying to delete a row in {type(self)} but deletions_enabled is set to False.

What it means

Raised by Pathway's Python input connector when a custom ConnectorSubject calls subject.remove(key, ...) while the connector was created with deletions_enabled=False. Deletions are opt-in for python connectors, so emitting a retract on an append-only connector is rejected at the source rather than dropped silently, which would desynchronize downstream state from reality.

Source

Thrown at python/pathway/io/python/__init__.py:286

        if self._session_type == SessionType.NATIVE:
            self._buffer.put((PythonConnectorEventType.INSERT, key, values))
        elif self._session_type == SessionType.UPSERT:
            if not self._deletions_enabled:
                raise ValueError(
                    f"Trying to modify a row in {type(self)} but deletions_enabled is set to False."
                )
            self._buffer.put((PythonConnectorEventType.INSERT, key, values))
        else:
            raise NotImplementedError(f"session type {self._session_type} not handled")

    def _remove(
        self, key: Pointer, message: bytes, metadata: bytes | None = None
    ) -> None:
        self._remove_inner(key, self._get_values_dict(message, metadata))

    def _remove_inner(self, key: Pointer | None, values: dict[str, Any]) -> None:
        if not self._deletions_enabled:
            raise ValueError(
                f"Trying to delete a row in {type(self)} but deletions_enabled is set to False."
            )
        self._buffer.put((PythonConnectorEventType.DELETE, key, values))

    def _read(self) -> Any:
        """Allows to retrieve data from a buffer.

        Should not be called directly.
        """
        return self._buffer.get()

    def _is_internal(self) -> bool:
        """
        The Python connector is internal in case it is used to implement an internal
        The Pathway Live Data Framework feature rather than to read the data from an external source.

        We need this distinction, because internal usages don't read user data and
        aren't a part of the external perimeter, which is currently persisted. Therefore

View on GitHub (pinned to fa2f74a464)

Solutions

  1. Pass deletions_enabled=True to pw.io.python.read so remove() is honored.
  2. If deletions must stay disabled, filter tombstones inside the subject's run() and never call remove().
  3. For batch/static sources that finish and never retract, keep deletions_enabled=False and simply omit any delete emissions.

Example fix

# before
def run(self):
    for key, payload, deleted in feed:
        if deleted:
            self.remove(key, payload)  # not allowed: deletions disabled
pw.io.python.read(Subj(), schema=S, deletions_enabled=False)

# after
pw.io.python.read(Subj(), schema=S)  # deletions_enabled=True
Defensive patterns

Strategy: validation

Validate before calling

source_can_delete = True
pw.io.python.read(Subj(), schema=S, deletions_enabled=source_can_delete)

Prevention

When it happens

Trigger: A ConnectorSubject.run() that calls self.remove(key, payload) for tombstoned events, while pw.io.python.read(subject, ..., deletions_enabled=False) was configured (e.g. to reduce memory usage in static workloads).

Common situations: Reading a changelog/DEBEZIUM-style feed where deletes appear, with deletions_enabled=False chosen for performance; upgrading an append-only prototype to a real source that now sends tombstones without flipping the flag back.

Related errors


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