apache/beam · error · TypeError

Replacement string %since% not found on custom message

Error message

Replacement string %since% not found on custom message

What it means

When a deprecated/experimental function is declared with a custom_message but the label is 'deprecated', the message must contain the %since% placeholder so the version string can be substituted. Beam raises TypeError if it is missing, because the deprecation notice would lose its version information.

Solutions

  1. Add %since% to the custom_message string.
  2. Switch the label to 'experimental' if no version info is intended.
  3. Remove custom_message and let Beam build the default message from since/current.
  4. Include both placeholders: '%since%' and optionally '%current%'.

Example fix

// before
@deprecated(since='2.0.0', current='new_fn', custom_message='Use new_fn.')
// after
@deprecated(since='2.0.0', current='new_fn', custom_message='Deprecated since %since%. Use %current%.')
Defensive patterns

Strategy: validation

Validate before calling

if label == 'deprecated' and '%since%' not in custom_message:
    raise ValueError('custom_message must contain %since%')

Try / catch

try:
    wrap = deprecated(since=since, current=cur, custom_message=msg)
except TypeError as e:
    log.warning('fixing custom_message: %s', e)
    wrap = deprecated(since=since, current=cur, custom_message=msg + ' Deprecated since %since%.')

Prevention

When it happens

Trigger: Calling @deprecated(since='X', current='y', custom_message='Use y.') without '%since%' in custom_message, then applying the decorator.

Common situations: Copy-pasting a custom message from an @experimental decorator into an @deprecated decorator; hand-editing a deprecation message and dropping the placeholder; upgrading Beam where stricter checks apply.

Understand the failure class

Background: "is deprecated and will be removed" — deprecation warnings for old API names, keywords, and options, and how to migrate before the removal release — this error's family across 29 libraries.

Related errors


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

Appendix: source

Thrown at sdks/python/apache_beam/utils/annotations.py:104

# Don't ignore BeamDeprecationWarnings.
warnings.simplefilter('once', BeamDeprecationWarning)


class _WarningMessage:
  """Utility class for assembling the warning message."""
  def __init__(self, label, since, current, extra_message, custom_message):
    """Initialize message, leave only name as placeholder."""
    if custom_message is None:
      message = '%name% is ' + label
      if label == 'deprecated':
        message += ' since %s' % since
      message += '. Use %s instead.' % current if current else '.'
      if extra_message:
        message += ' ' + extra_message
    else:
      if label == 'deprecated' and '%since%' not in custom_message:
        raise TypeError(
            "Replacement string %since% not found on \
        custom message")
      emptyArg = lambda x: '' if x is None else x
      message = custom_message\
      .replace('%since%', emptyArg(since))\
      .replace('%current%', emptyArg(current))\
      .replace('%extra%', emptyArg(extra_message))
    self.label = label
    self.message = message

  def emit_warning(self, fnc_name):
    if self.label == 'deprecated':
      warning_type = BeamDeprecationWarning
    else:
      warning_type = FutureWarning
    warnings.warn(
        self.message.replace('%name%', fnc_name), warning_type, stacklevel=3)

View on GitHub (pinned to 12126d8942)