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
- Default optional delays to 0 (or use loop.call_soon for immediate scheduling) instead of None
- Validate before scheduling: if delay is None, raise or substitute a sensible default in your own code
- Type-annotate delay as float and check isinstance(delay, (int, float)) at your API boundary
- 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
- Default optional delay parameters to 0, not None
- Use loop.call_soon when you mean 'as soon as possible'
- Validate config-sourced timing values at load time
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
- task factory must be a callable or None
- when cannot be None
- count must be a positive integer (got {!r})
- AIX_BUILDDATE is not defined or invalid: {build_date!r}
- expected str, bytes or os.PathLike object, not {path_type.__
AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17).
Data as JSON: /api/errors/79e19c22113406a8.
Report an issue: GitHub.