Skip to content

Periodic

periodic

Run a function every N seconds in the background.

periodic is a tiny background loop, not a scheduler: no cron expressions, no persistence, no catch-up. The decorated function stays directly callable and gains start(), stop() and a few read-only attributes.

Timing rules:

  • The schedule is real time. fast_mode() and fake_clock() never shorten it (they only affect backoff waits, see python_wrap.testing); a periodic loop that ran "instantly" would spin. Test with tiny intervals, or call the function directly.
  • Intervals are measured start to start and do not drift: the n-th tick is due at first + n * interval. If a run is still going when the next tick is due, the missed ticks are skipped (never run in a burst) and the loop resumes at the next tick.
  • jitter moves each run by a uniformly random offset of up to ± jitter * interval around its tick. Jitter never accumulates, because ticks are computed without it.

Periodic

Periodic(func: Callable[P, R], *config: Any)

Bases: _Base[P, R]

A function that runs in a background daemon thread; see periodic.

Calling the object calls the wrapped function directly. Attributes: runs, last_error, running.

running property

running: bool

True while the background thread is alive.

start

start(*args: args, **kwargs: kwargs) -> Periodic[P, R]

Start the background thread; args and kwargs go to every run.

Returns:

Type Description
Periodic[P, R]

This object, so f.start() can be chained.

Raises:

Type Description
RuntimeError

If it is already running.

stop

stop(timeout: float | None = 5.0) -> bool

Stop the loop and wait up to timeout seconds for the thread to end.

A run that is in progress is allowed to finish (a thread cannot be interrupted). Calling it again, or on a loop that never started, is harmless. Called from inside the function itself it only requests the stop.

Parameters:

Name Type Description Default
timeout float | None

Seconds to wait for the thread; None waits forever.

5.0

Returns:

Type Description
bool

True if the thread has ended, False if it is still finishing a run.

Raises:

Type Description
Exception

With on_error="raise", the exception that ended the loop (raised once).

running_in_background

running_in_background(*args: args, **kwargs: kwargs) -> Iterator[Periodic[P, R]]

Start the loop for the duration of a with block, then stop it.

AsyncPeriodic

AsyncPeriodic(func: Callable[P, Coroutine[Any, Any, R]], *config: Any)

Bases: _Base[P, R]

An async def function that runs as a task on the current loop; see periodic.

Calling the object calls the coroutine function directly. Attributes: runs, last_error, running.

running property

running: bool

True while the background task has not finished.

start

start(*args: args, **kwargs: kwargs) -> AsyncPeriodic[P, R]

Start the background task on the running loop; arguments go to every run.

Returns:

Type Description
AsyncPeriodic[P, R]

This object, so f.start() can be chained.

Raises:

Type Description
RuntimeError

If called outside a running event loop, or if already running.

stop async

stop() -> bool

Cancel the task (including a run in progress) and wait for it to end.

Calling it again, or on a loop that never started, is harmless. Called from inside the function itself it ends the loop as soon as that run returns. If the task awaiting it is cancelled, the cancellation propagates at once and the loop task finishes in the background.

Returns:

Type Description
bool

True once the task has ended.

Raises:

Type Description
Exception

With on_error="raise", the exception that ended the loop (raised once).

running_in_background async

running_in_background(*args: args, **kwargs: kwargs) -> AsyncIterator[AsyncPeriodic[P, R]]

Start the loop for the duration of an async with block, then stop it.

periodic

periodic(interval: float, *, jitter: float = 0.0, initial_delay: float = 0.0, max_runs: int | None = None, on_error: OnError = 'log', name: str | None = None, logger: LoggerLike = None, level: int = logging.ERROR) -> _Decorator

Run the decorated function every interval seconds in the background.

The function stays directly callable. The decorated object adds start(), stop(), running, runs, last_error and running_in_background() (a context manager that starts and stops cleanly). A plain function runs in a daemon thread; an async def function runs as a task on the current event loop (start() must be called inside it, and await f.stop() ends it). start() arguments are passed to every run, and the caller's contextvars are copied.

Runs start every interval seconds measured start to start, without drift. A run that outlasts the interval makes the loop skip the missed ticks instead of running in a burst. The schedule is real time: fast_mode and fake_clock do not shorten it.

To use it on a method, decorate the bound method: periodic(5)(self.refresh). Decorating a function that takes self or cls first is rejected.

Parameters:

Name Type Description Default
interval float

Seconds between run starts. Must be greater than zero.

required
jitter float

Fraction of interval (0 to less than 1) by which each run is randomly moved earlier or later, e.g. 0.1 is +/-10 percent.

0.0
initial_delay float

Seconds before the first run (no jitter). Default: run at once.

0.0
max_runs int | None

Stop by itself after this many runs (failed runs count). Default: forever.

None
on_error OnError

What an exception in a run does. "log" logs it and carries on; "stop" logs it and ends the loop; "raise" ends the loop and re-raises the exception, once, from stop() (so it never vanishes in a thread; if start() is called again first, it is logged at ERROR instead). In every mode it is kept in last_error and published as a periodic.failed event.

'log'
name str | None

Name for logs, events and the thread. Default: the function's qualified name.

None
logger LoggerLike

Logger, logger name or None for the package logger.

None
level int

Level for failure logs. Default: logging.ERROR.

ERROR

Returns:

Type Description
_Decorator

A decorator producing a Periodic (plain function) or AsyncPeriodic

_Decorator

(async def).

Raises:

Type Description
ValueError

For an invalid interval, jitter, initial_delay, max_runs or on_error.

TypeError

When decorating something that is not callable, or an unbound method.

Events

periodic.run (fields run, seconds) and periodic.failed (fields run, exception).

Example
from python_wrap.periodic import periodic

@periodic(0.01, max_runs=3)
def tick() -> None:
    ...

with tick.running_in_background():
    pass