{"record":{"id":"bdfd51a07c36d9b3","repo":"apache/beam","slug":"end-offset-must-not-be-none","errorCode":null,"errorMessage":"End offset must not be 'None'","messagePattern":"End offset must not be 'None'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/io/range_trackers.py","lineNumber":56,"sourceCode":"\n\nclass OffsetRangeTracker(iobase.RangeTracker):\n  \"\"\"A 'RangeTracker' for non-negative positions of type 'long'.\"\"\"\n\n  # Offset corresponding to infinity. This can only be used as the upper-bound\n  # of a range, and indicates reading all of the records until the end without\n  # specifying exactly what the end is.\n  # Infinite ranges cannot be split because it is impossible to estimate\n  # progress within them.\n  OFFSET_INFINITY = float('inf')\n\n  def __init__(self, start, end):\n    super().__init__()\n\n    if start is None:\n      raise ValueError('Start offset must not be \\'None\\'')\n    if end is None:\n      raise ValueError('End offset must not be \\'None\\'')\n    assert isinstance(start, int)\n    if end != self.OFFSET_INFINITY:\n      assert isinstance(end, int)\n\n    assert start <= end\n\n    self._start_offset = start\n    self._stop_offset = end\n\n    self._last_record_start = -1\n    self._last_attempted_record_start = -1\n    self._offset_of_last_split_point = -1\n    self._lock = threading.Lock()\n\n    self._split_points_seen = 0\n    self._split_points_unclaimed_callback = None\n\n  def start_position(self):","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/io/range_trackers.py#L38-L74","documentation":"OffsetRangeTracker requires an explicit end offset; None is not a representable position (unbounded end is expressed via the OFFSET_INFINITY sentinel, float('inf')). The constructor raises ValueError when end is None so range tracking and split logic never encounter an undefined position.","triggerScenarios":"OffsetRangeTracker(start, None) — constructing the tracker without an end. Use OffsetRangeTracker.OFFSET_INFINITY for unbounded ranges.","commonSituations":"Custom bounded/unbounded source implementations passing None instead of the infinity sentinel; computing end from file size that failed to resolve.","solutions":["Pass OFFSET_INFINITY for an unbounded end: OffsetRangeTracker(start, OffsetRangeTracker.OFFSET_INFINITY).","Pass a concrete integer end offset.","Fix upstream computation of end so it never returns None; validate before constructing."],"exampleFix":"// before\nOffsetRangeTracker(start, None)\n// after\nOffsetRangeTracker(start, OffsetRangeTracker.OFFSET_INFINITY)","handlingStrategy":"type-guard","validationCode":"assert end_offset is not None, \"end offset required\"\nif end_offset is None:\n    end_offset = OffsetRangeTracker.OFFSET_INFINITY","typeGuard":"def is_valid_end(x) -> bool:\n    return x == float('inf') or (isinstance(x, int) and not isinstance(x, bool))","tryCatchPattern":"try:\n    tracker = OffsetRangeTracker(start, end)\nexcept ValueError as e:\n    logger.error(\"Bad offset range: %s\", e)\n    tracker = OffsetRangeTracker(start, OffsetRangeTracker.OFFSET_INFINITY)","preventionTips":["Use OFFSET_INFINITY (float('inf')) for unbounded ends, never None.","Validate computed end offsets (e.g. file sizes) before constructing.","Keep source range computation free of silent None returns."],"tags":["python","apache-beam","io","range-tracker","null"],"backgroundTag":"null-argument","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}