{"record":{"id":"42665b720aa7ae74","repo":"apache/beam","slug":"on-timer-decorator-expected-callable","errorCode":null,"errorMessage":"@on_timer decorator expected callable.","messagePattern":"@on_timer decorator expected callable\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/transforms/userstate.py","lineNumber":237,"sourceCode":"  \"\"\"Decorator for timer firing DoFn method.\n\n  This decorator allows a user to specify an on_timer processing method\n  in a stateful DoFn.  Sample usage::\n\n    class MyDoFn(DoFn):\n      TIMER_SPEC = TimerSpec('timer', TimeDomain.WATERMARK)\n\n      @on_timer(TIMER_SPEC)\n      def my_timer_expiry_callback(self):\n        logging.info('Timer expired!')\n  \"\"\"\n\n  if not isinstance(timer_spec, TimerSpec):\n    raise ValueError('@on_timer decorator expected TimerSpec.')\n\n  def _inner(method: CallableT) -> CallableT:\n    if not callable(method):\n      raise ValueError('@on_timer decorator expected callable.')\n    if timer_spec._attached_callback:\n      raise ValueError(\n          'Multiple on_timer callbacks registered for %r.' % timer_spec)\n    timer_spec._attached_callback = method\n    return method\n\n  return _inner\n\n\ndef get_dofn_specs(dofn: 'DoFn') -> tuple[set[StateSpec], set[TimerSpec]]:\n  \"\"\"Gets the state and timer specs for a DoFn, if any.\n\n  Args:\n    dofn (apache_beam.transforms.core.DoFn): The DoFn instance to introspect for\n      timer and state specs.\n  \"\"\"\n\n  # Avoid circular import.","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/transforms/userstate.py#L219-L255","documentation":"The function returned by @on_timer validates that the method being decorated is callable before attaching it to the TimerSpec. If the object under the decorator is not callable (e.g. a property, a constant, or a value), the decorator raises ValueError instead of silently installing a broken callback.","triggerScenarios":"Applying @on_timer(TIMER_SPEC) on top of a non-callable expression, e.g. stacking decorators that replace the method with a value, or accidentally decorating a class attribute instead of a method.","commonSituations":"Decorator-order mistakes (e.g. combining @staticmethod/@property with @on_timer in the wrong order); partially edited code where the def line was removed but the decorator remained.","solutions":["Decorate a real method: keep @on_timer(TIMER_SPEC) directly above a def with self as first parameter","Check decorator ordering — @on_timer should wrap the actual function (place non-callable-returning decorators appropriately)","Remove stray decorators that turn the method into a non-callable"],"exampleFix":"// before\n@on_timer(TIMER_SPEC)\n@staticmethod\ndef expiry(self): ...\n// after\n@on_timer(TIMER_SPEC)\ndef expiry(self): ...","handlingStrategy":"validation","validationCode":"assert callable(method_to_decorate), '@on_timer must decorate a callable method'","typeGuard":"def is_callable_method(m) -> bool:\n    return callable(m)","tryCatchPattern":"try:\n    _ = MyDoFn()\nexcept ValueError as e:\n    if 'expected callable' in str(e):\n        log.error('check decorator ordering on timer callback')","preventionTips":["Place @on_timer(SPEC) directly above a def statement","Avoid stacking decorators that produce non-callables (staticmethod/property ordering)","Keep timer callbacks as plain instance methods taking self"],"tags":["python","apache-beam","timers","decorator","callable"],"backgroundTag":"invalid-argument-value","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}