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
- Add %since% to the custom_message string.
- Switch the label to 'experimental' if no version info is intended.
- Remove custom_message and let Beam build the default message from since/current.
- 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
- Always include '%since%' (and '%current%') in deprecation custom messages
- Reuse a message template constant instead of hand-writing messages
- Don't reuse @experimental messages for @deprecated decorators
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
- chunk_to_dict_fn is deprecated, use embeddable_to_dict_fn
- Dicom Client moved to…
- Dicom IO moved to apache_beam.io.gcp.healthcare.dicomcio
- Kafka transforms moved to apache_beam.io.kafka
- Native sinks no longer implemented; falling back to…
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)