apache/beam · error · ValueError

%r cannot be represented exactly at precision

Error message

%r cannot be represented exactly at precision %d. Set allow_lossy_conversion=True to truncate it.

What it means

Timestamp.to_precision() refuses to narrow a timestamp's precision when doing so would discard non-zero digits, unless you pass allow_lossy_conversion=True. This prevents silently losing sub-unit data when truncating (flooring) to a coarser precision.

Solutions

  1. Pass allow_lossy_conversion=True if truncation is acceptable: ts.to_precision(6, allow_lossy_conversion=True).
  2. Check first whether the conversion is lossless (e.g. compare nanos % 1000 == 0) and handle the remainder explicitly.
  3. Keep the timestamp at its original precision and only round at output/formatting time (to_rfc3339).

Example fix

// before
micro_ts = nano_ts.to_precision(6)
// after
micro_ts = nano_ts.to_precision(6, allow_lossy_conversion=True)
Defensive patterns

Strategy: validation

Validate before calling

def truncatable(ts, target):
    return ts.precision <= target or ts.nanos % (10 ** (ts.precision - target)) == 0

Try / catch

try:
    return ts.to_precision(target)
except ValueError:
    return ts.to_precision(target, allow_lossy_conversion=True)

Prevention

When it happens

Trigger: Calling ts.to_precision(p) with p < ts._precision when the truncated subsecond digits are non-zero and allow_lossy_conversion is not set, e.g. Timestamp(0, 500_000_000, 9).to_precision(6).

Common situations: Downsampling nanosecond-precision event timestamps to microsecond windows; converting beam timestamps to external systems that only store micros; user code that assumes truncation is silent like Python's int().

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at sdks/python/apache_beam/utils/timestamp.py:267

    Increasing precision is always lossless. Decreasing precision raises
    ValueError if this timestamp has a non-zero component below the target
    precision, unless allow_lossy_conversion is True, in which case the
    timestamp is truncated (floored) to the target precision.
    """
    if precision == self._precision:
      return self
    if not 0 <= precision <= Timestamp.NANOS_PRECISION:
      raise ValueError(
          'Timestamp precision must be between 0 and %d (inclusive), '
          'but was %d.' % (Timestamp.NANOS_PRECISION, precision))
    if precision > self._precision:
      scale = _POW_10[precision - self._precision]
      return Timestamp(self._seconds, self._subseconds * scale, precision)
    scale = _POW_10[self._precision - precision]
    remainder = self._subseconds % scale
    if remainder and not allow_lossy_conversion:
      raise ValueError(
          '%r cannot be represented exactly at precision %d. Set '
          'allow_lossy_conversion=True to truncate it.' % (self, precision))
    return Timestamp(self._seconds, self._subseconds // scale, precision)

  def predecessor(self) -> 'Timestamp':
    """Returns the largest timestamp smaller than self, at this precision."""
    return Timestamp(self._seconds, self._subseconds - 1, self._precision)

  def successor(self) -> 'Timestamp':
    """Returns the smallest timestamp larger than self, at this precision."""
    return Timestamp(self._seconds, self._subseconds + 1, self._precision)

  def __repr__(self) -> str:
    total = self._total(self._precision)
    sign = ''
    if total < 0:
      sign = '-'
      total = -total

View on GitHub (pinned to 12126d8942)