RustPython/RustPython · error · TypeError

delay must not be None

Error message

delay must not be None

What it means

loop.call_later(delay, callback, *args) requires a numeric delay in seconds. None is explicitly rejected with TypeError('delay must not be None') so that a missing delay fails fast with a clear message instead of the more obscure error that time() + None would produce.

Source

Thrown at Lib/asyncio/base_events.py:792

    def call_later(self, delay, callback, *args, context=None):
        """Arrange for a callback to be called at a given time.

        Return a Handle: an opaque object with a cancel() method that
        can be used to cancel the call.

        The delay can be an int or float, expressed in seconds.  It is
        always relative to the current time.

        Each callback will be called exactly once.  If two callbacks
        are scheduled for exactly the same time, it is undefined which
        will be called first.

        Any positional arguments after the callback will be passed to
        the callback when it is called.
        """
        if delay is None:
            raise TypeError('delay must not be None')
        timer = self.call_at(self.time() + delay, callback, *args,
                             context=context)
        if timer._source_traceback:
            del timer._source_traceback[-1]
        return timer

    def call_at(self, when, callback, *args, context=None):
        """Like call_later(), but uses an absolute time.

        Absolute time corresponds to the event loop's time() method.
        """
        if when is None:
            raise TypeError("when cannot be None")
        self._check_closed()
        if self._debug:
            self._check_thread()
            self._check_callback(callback, 'call_at')
        timer = events.TimerHandle(when, callback, args, self, context)

View on GitHub (pinned to aaeab4f754)

Solutions

  1. Default optional delays to 0 (or use loop.call_soon for immediate scheduling) instead of None
  2. Validate before scheduling: if delay is None, raise or substitute a sensible default in your own code
  3. Type-annotate delay as float and check isinstance(delay, (int, float)) at your API boundary
  4. Trace where the None originated — usually an optional config or keyword argument

Example fix

# before
# def schedule(delay, cb):
#     loop.call_later(delay, cb)  # delay=None -> TypeError

# after
# def schedule(delay, cb):
#     loop.call_later(delay or 0, cb)
Defensive patterns

Strategy: validation

Validate before calling

if delay is None:
    delay = 0.0
elif not isinstance(delay, (int, float)):
    raise TypeError(f'delay must be a number, got {delay!r}')
loop.call_later(delay, callback)

Type guard

def is_delay(value) -> bool:
    return isinstance(value, (int, float))

Prevention

When it happens

Trigger: loop.call_later(delay, cb) where delay came from an optional parameter defaulted to None; passing None from configuration or a lookup that failed to produce a number.

Common situations: Wrapper functions like 'def retry(delay=None)' forwarding to call_later; config values that are absent and surface as None; refactors that change a delay's default from 0 to None.

Related errors


AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17). Data as JSON: /api/errors/79e19c22113406a8. Report an issue: GitHub.