apache/beam · error · ValueError

Split result must be a tuple that contains split position an

Error message

Split result must be a tuple that contains split position and split fraction. Received: %r

What it means

Raised inside _assert_split_at_fraction_behavior (used by assert_split_at_fraction_behavior and assert_split_at_fraction_binary) when RangeTracker.try_split() returns a non-None result that is not a 2-element tuple of (split_position, split_fraction). A successful split must return exactly two values per the iobase.RangeTracker contract. This indicates a custom RangeTracker implementation violating the try_split return contract.

Source

Thrown at sdks/python/apache_beam/io/source_test_utils.py:303

    stop_position=None):

  range_tracker = source.get_range_tracker(start_position, stop_position)
  assert isinstance(range_tracker, iobase.RangeTracker)
  current_items = []
  reader = source.read(range_tracker)
  # Reading 'num_items_to_read_before_split' items.
  reader_iter = iter(reader)
  for _ in range(num_items_to_read_before_split):
    current_items.append(next(reader_iter))

  suggested_split_position = range_tracker.position_at_fraction(split_fraction)

  stop_position_before_split = range_tracker.stop_position()
  split_result = range_tracker.try_split(suggested_split_position)

  if split_result is not None:
    if len(split_result) != 2:
      raise ValueError(
          'Split result must be a tuple that contains split '
          'position and split fraction. Received: %r' % (split_result, ))

    if range_tracker.stop_position() != split_result[0]:
      raise ValueError(
          'After a successful split, the stop position of the '
          'RangeTracker must be the same as the returned split '
          'position. Observed %r and %r which are different.' %
          (range_tracker.stop_position() % (split_result[0], )))

    if split_fraction < 0 or split_fraction > 1:
      raise ValueError(
          'Split fraction must be within the range [0,1]',
          'Observed split fraction was %r.' % (split_result[1], ))

  stop_position_after_split = range_tracker.stop_position()
  if split_result and stop_position_after_split == stop_position_before_split:
    raise ValueError(

View on GitHub (pinned to 12126d8942)

Solutions

  1. Return a 2-tuple (split_position, split_fraction) from try_split on success.
  2. Return None (not an empty tuple/list) when the split cannot be performed.
  3. Prefer subclassing apache_beam.io.iobase.RangeTracker, which enforces the contract.
  4. Log repr(split_result) (shown in the message) to see the actual returned structure.

Example fix

# before
def try_split(self, fraction):
  return self.position_at_fraction(fraction)
# after
def try_split(self, fraction):
  pos = self.position_at_fraction(fraction)
  if pos is None:
    return None
  return (pos, fraction)
Defensive patterns

Strategy: type-guard

Validate before calling

res = tracker.try_split(pos)
if res is not None and not (isinstance(res, tuple) and len(res) == 2):
    raise TypeError("try_split must return (position, fraction) or None")

Type guard

def is_valid_split_result(res):
    return res is None or (isinstance(res, tuple) and len(res) == 2)

Try / catch

try:
    source_test_utils.assert_split_at_fraction_behavior(src, n, f, outcome)
except ValueError as e:
    if "Split result must be a tuple" in str(e):
        logger.error("bad try_split return: %s", e)
    raise

Prevention

When it happens

Trigger: A custom RangeTracker's try_split returns None-of-length-2 structures such as a single value, a 3-tuple, a list with extra items, or a non-sized object, while the test harness performs try_split after reading num_items_to_read_before_split items.

Common situations: Implementing a custom RangeTracker for a new connector and returning only the new position from try_split, or returning (position, fraction, old_stop).

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/7368042f72ac2e08. Report an issue: GitHub.