mode — mode-ng 0.4.0 documentation

# `mode`

AsyncIO Service-based programming.

class mode.BaseSignal(*, name: Optional[str] = None, owner: Optional[Type] = None, loop: Optional[AbstractEventLoop] = None, default_sender: Optional[Any] = None, receivers: Optional[MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]] = None, filter_receivers: Optional[MutableMapping[Any, MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]]] = None)

  Base class for signal/observer pattern.

  asdict() → Mapping[str, Any]
  clone(**kwargs: Any) → BaseSignalT
  connect(fun: Optional[Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]] = None, **kwargs: Any) → Callable
  disconnect(fun: Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]], *, weak: bool = False, sender: Optional[Any] = None) → None
  property ident: str
  iter_receivers(sender: T_contra) → Iterable[Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]]
  property label: str
  unpack_sender_from_args(*args: Any) → tuple[T, tuple[Any, ...]]
  with_default_sender(sender: Optional[Any] = None) → BaseSignalT

class mode.BaseSignalT(*, name: Optional[str] = None, owner: Optional[Type] = None, loop: Optional[AbstractEventLoop] = None, default_sender: Optional[Any] = None, receivers: Optional[MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]] = None, filter_receivers: Optional[MutableMapping[Any, MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]]] = None)

  Base type for all signals.

  abstract clone(**kwargs: Any) → BaseSignalT
  abstract connect(fun: Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]], **kwargs: Any) → Callable
  abstract disconnect(fun: Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]], *, sender: Optional[Any] = None, weak: bool = True) → None
  name: str
  owner: Optional[Type]
  abstract with_default_sender(sender: Optional[Any] = None) → BaseSignalT

class mode.CrashingSupervisor(*services: ~mode.types.services.ServiceT, max_restarts: ~typing.Union[~datetime.timedelta, int, float, str] = 100.0, over: ~typing.Union[~datetime.timedelta, int, float, str] = 1.0, raises: ~typing.Type[BaseException] = <class 'mode.exceptions.MaxRestartsExceeded'>, replacement: ~typing.Optional[~typing.Callable[[~mode.types.services.ServiceT, int], ~typing.Awaitable[~mode.types.services.ServiceT]]] = None, **kwargs: ~typing.Any)

  Supervisor that crashes the whole program.

  logger: logging.Logger = <Logger mode.supervisors (WARNING)>
  wakeup() → None

class mode.ForfeitOneForAllSupervisor(*services: ~mode.types.services.ServiceT, max_restarts: ~typing.Union[~datetime.timedelta, int, float, str] = 100.0, over: ~typing.Union[~datetime.timedelta, int, float, str] = 1.0, raises: ~typing.Type[BaseException] = <class 'mode.exceptions.MaxRestartsExceeded'>, replacement: ~typing.Optional[~typing.Callable[[~mode.types.services.ServiceT, int], ~typing.Awaitable[~mode.types.services.ServiceT]]] = None, **kwargs: ~typing.Any)

  If one service in the group crashes, we give up on all of them.

  logger: logging.Logger = <Logger mode.supervisors (WARNING)>
  async restart_services(services: list[mode.types.services.ServiceT]) → None

class mode.ForfeitOneForOneSupervisor(*services: ~mode.types.services.ServiceT, max_restarts: ~typing.Union[~datetime.timedelta, int, float, str] = 100.0, over: ~typing.Union[~datetime.timedelta, int, float, str] = 1.0, raises: ~typing.Type[BaseException] = <class 'mode.exceptions.MaxRestartsExceeded'>, replacement: ~typing.Optional[~typing.Callable[[~mode.types.services.ServiceT, int], ~typing.Awaitable[~mode.types.services.ServiceT]]] = None, **kwargs: ~typing.Any)

  Supervisor that if a service crashes, we do not restart it.

  logger: logging.Logger = <Logger mode.supervisors (WARNING)>
  async restart_services(services: list[mode.types.services.ServiceT]) → None

class mode.OneForAllSupervisor(*services: ~mode.types.services.ServiceT, max_restarts: ~typing.Union[~datetime.timedelta, int, float, str] = 100.0, over: ~typing.Union[~datetime.timedelta, int, float, str] = 1.0, raises: ~typing.Type[BaseException] = <class 'mode.exceptions.MaxRestartsExceeded'>, replacement: ~typing.Optional[~typing.Callable[[~mode.types.services.ServiceT, int], ~typing.Awaitable[~mode.types.services.ServiceT]]] = None, **kwargs: ~typing.Any)

  Supervisor that restarts all services when a service crashes.

  logger: logging.Logger = <Logger mode.supervisors (WARNING)>
  async restart_services(services: list[mode.types.services.ServiceT]) → None

class mode.OneForOneSupervisor(*services: ~mode.types.services.ServiceT, max_restarts: ~typing.Union[~datetime.timedelta, int, float, str] = 100.0, over: ~typing.Union[~datetime.timedelta, int, float, str] = 1.0, raises: ~typing.Type[BaseException] = <class 'mode.exceptions.MaxRestartsExceeded'>, replacement: ~typing.Optional[~typing.Callable[[~mode.types.services.ServiceT, int], ~typing.Awaitable[~mode.types.services.ServiceT]]] = None, **kwargs: ~typing.Any)

  Supervisor simply restarts any crashed service.

  logger: logging.Logger = <Logger mode.supervisors (WARNING)>

class mode.Service(*, beacon: Optional[NodeT] = None, loop: Optional[AbstractEventLoop] = None)

  An asyncio service that can be started/stopped/restarted.

  Keyword Arguments:

    - beacon (NodeT) – Beacon used to track services in a graph.

    - loop (AbstractEventLoop) – Event loop object.

  class Diag(service: ServiceT)

    Service diagnostics.

    This can be used to track what your service is doing. For example if your service is a Kafka consumer with a background thread that commits the offset every 30 seconds, you may want to see when this happens:

        DIAG_COMMITTING = 'committing'

        class Consumer(Service):

            @Service.task
            async def _background_commit(self) -> None:
                while not self.should_stop:
                    await self.sleep(30.0)
                    self.diag.set_flag(DIAG_COMITTING)
                    try:
                        await self._consumer.commit()
                    finally:
                        self.diag.unset_flag(DIAG_COMMITTING)

    The above code is setting the flag manually, but you can also use a decorator to accomplish the same thing:

        @Service.timer(30.0)
        async def _background_commit(self) -> None:
            await self.commit()

        @Service.transitions_with(DIAG_COMITTING)
        async def commit(self) -> None:
            await self._consumer.commit()

    set_flag(flag: str) → None
    unset_flag(flag: str) → None
  abstract: ClassVar[bool] = False

    Set to True if this service class is abstract-only, meaning it will only be used as a base class.

  async add_async_context(context: AsyncContextManager) → Any
  add_context(context: ContextManager) → Any
  add_dependency(service: ServiceT) → ServiceT

    Add dependency to other service.

    The service will be started/stopped with this service.

  add_future(coro: Awaitable) → Future

    Add relationship to asyncio.Future.

    The future will be joined when this service is stopped.

  async add_runtime_dependency(service: ServiceT) → ServiceT
  property beacon: NodeT

    Beacon used to track services in a dependency graph.

  async crash(reason: BaseException) → None

    Crash the service and all child services.

  property crash_reason: Optional[BaseException]
  property crashed: bool
  classmethod crontab(cron_format: str, *, timezone: Optional[tzinfo] = None) → Callable[[Callable], ServiceTask]

    Background timer executing periodic task based on Crontab description.

    Example

        >>> class S(Service):
        ...
        ...     @Service.crontab(cron_format='30 18 * * *',
                                 timezone=pytz.timezone('US/Pacific'))
        ...     async def every_6_30_pm_pacific(self):
        ...         print('IT IS 6:30pm')
        ...
        ...     @Service.crontab(cron_format='30 18 * * *')
        ...     async def every_6_30_pm(self):
        ...         print('6:30pm UTC')

  classmethod from_awaitable(coro: Awaitable, *, name: Optional[str] = None, **kwargs: Any) → ServiceT
  human_tracebacks() → str
  async itertimer(interval: ~typing.Union[~datetime.timedelta, int, float, str], *, max_drift_correction: float = 0.1, sleep: ~typing.Optional[~typing.Callable[[...], ~typing.Awaitable]] = None, clock: ~typing.Callable[[], float] = <built-in function perf_counter>, name: str = '') → AsyncIterator[float]

    Sleep `interval` seconds for every iteration.

    This is an async iterator that takes advantage of `Timer()` to monitor drift and timer oerlap.

    Uses `Service.sleep` so exits fast when the service is stopped.

    Note

    Will sleep the full interval seconds before returning from first iteration.

    Examples

        >>> async for sleep_time in self.itertimer(1.0):
        ...   print('another second passed, just woke up...')
        ...   await perform_some_http_request()

  async join_services(services: Sequence[ServiceT]) → None
  property label: str

    Label used for graphs.

  logger: logging.Logger = <Logger mode.services (WARNING)>
  async maybe_start() → bool

    Start the service, if it has not already been started.

  mundane_level = 'info'

    The log level for mundane info such as starting, stopping, etc. Set this to `"debug"` for less information.

  on_init_dependencies() → Iterable[ServiceT]

    Return list of service dependencies for this service.

  async remove_dependency(service: ServiceT) → ServiceT

    Stop and remove dependency of this service.

  async restart() → None

    Restart this service.

  restart_count: int = 0

    Current number of times this service instance has been restarted.

  service_reset() → None
  set_shutdown() → None

    Set the shutdown signal.

    Notes

    If `wait_for_shutdown` is set, stopping the service will wait for this flag to be set.

  property shortlabel: str

    Label used for logging.

  property should_stop: bool

    Return `True` if the service must stop.

  shutdown_timeout: float = 60.0

    Time to wait for shutdown flag set before we give up.

  async sleep(n: Union[timedelta, int, float, str]) → None

    Sleep for `n` seconds, or until service stopped.

  async start() → None
  property started: bool

    Return `True` if the service was started.

  property state: str

    Service state - as a human readable string.

  async stop() → None

    Stop the service.

  classmethod task(fun: Callable[[Any], Awaitable[None]]) → ServiceTask

    Decorate function to be used as background task.

    Example

        >>> class S(Service):
        ...
        ...     @Service.task
        ...     async def background_task(self):
        ...         while not self.should_stop:
        ...             await self.sleep(1.0)
        ...             print('Waking up')

  classmethod timer(interval: Union[timedelta, int, float, str], *, exec_first: bool = False, name: Optional[str] = None, max_drift_correction: float = 0.1) → Callable[[Callable], ServiceTask]

    Background timer executing every `n` seconds.

    Example

        >>> class S(Service):
        ...
        ...     @Service.timer(1.0)
        ...     async def background_timer(self):
        ...         print('Waking up')

  tracebacks() → Mapping[str, str]
  async transition_with(flag: str, fut: Awaitable, *args: Any, **kwargs: Any) → Any
  classmethod transitions_to(flag: str) → Callable

    Decorate function to set and reset diagnostic flag.

  async wait(*coros: Union[Future, Generator[Any, None, Any], Awaitable, Event], timeout: Optional[Union[timedelta, int, float, str]] = None) → WaitResult

    Wait for coroutines to complete, or until the service stops.

  async wait_first(*coros: Union[Future, Generator[Any, None, Any], Awaitable, Event], timeout: Optional[Union[timedelta, int, float, str]] = None) → WaitResults
  wait_for_shutdown: bool = False

    Set to True if .stop must wait for the shutdown flag to be set.

  async wait_for_stopped(*coros: Union[Future, Generator[Any, None, Any], Awaitable, Event], timeout: Optional[Union[timedelta, int, float, str]] = None) → bool
  async wait_many(coros: Iterable[Union[Future, Generator[Any, None, Any], Awaitable, Event]], *, timeout: Optional[Union[timedelta, int, float, str]] = None) → WaitResult
  async wait_until_stopped() → None

    Wait until the service is signalled to stop.

class mode.ServiceT(*, beacon: Optional[NodeT] = None, loop: Optional[AbstractEventLoop] = None)

  Abstract type for an asynchronous service that can be started/stopped.

  See also

  `mode.Service`.

  Diag: Type[DiagT]
  abstract async add_async_context(context: AsyncContextManager) → Any
  abstract add_context(context: ContextManager) → Any
  abstract add_dependency(service: ServiceT) → ServiceT
  abstract async add_runtime_dependency(service: ServiceT) → ServiceT
  async_exit_stack: AsyncExitStack
  property beacon: NodeT
  abstract async crash(reason: BaseException) → None
  abstract property crash_reason: Optional[BaseException]
  abstract property crashed: bool
  diag: DiagT
  exit_stack: ExitStack
  abstract property label: str
  abstract property loop: AbstractEventLoop
  abstract async maybe_start() → bool
  abstract async restart() → None
  restart_count: int = 0
  abstract service_reset() → None
  abstract set_shutdown() → None
  abstract property shortlabel: str
  abstract property should_stop: bool
  shutdown_timeout: float
  abstract async start() → None
  abstract property started: bool
  abstract property state: str
  abstract async stop() → None
  supervisor: Optional[SupervisorStrategyT] = None
  wait_for_shutdown: bool = False
  abstract async wait_until_stopped() → None

class mode.Signal(*, name: Optional[str] = None, owner: Optional[Type] = None, loop: Optional[AbstractEventLoop] = None, default_sender: Optional[Any] = None, receivers: Optional[MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]] = None, filter_receivers: Optional[MutableMapping[Any, MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]]] = None)

  Asynchronous signal (using `async def` functions).

  clone(**kwargs: Any) → SignalT
  async send(*args: Any, **kwargs: Any) → None
  with_default_sender(sender: Any = None) → SignalT

class mode.SignalT(*, name: Optional[str] = None, owner: Optional[Type] = None, loop: Optional[AbstractEventLoop] = None, default_sender: Optional[Any] = None, receivers: Optional[MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]] = None, filter_receivers: Optional[MutableMapping[Any, MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]]] = None)

  Base class for all async signals (using `async def`).

  abstract clone(**kwargs: Any) → SignalT
  abstract async send(sender: T_contra, *args: Any, **kwargs: Any) → None
  abstract with_default_sender(sender: Any = None) → SignalT

class mode.SupervisorStrategy(*services: ~mode.types.services.ServiceT, max_restarts: ~typing.Union[~datetime.timedelta, int, float, str] = 100.0, over: ~typing.Union[~datetime.timedelta, int, float, str] = 1.0, raises: ~typing.Type[BaseException] = <class 'mode.exceptions.MaxRestartsExceeded'>, replacement: ~typing.Optional[~typing.Callable[[~mode.types.services.ServiceT, int], ~typing.Awaitable[~mode.types.services.ServiceT]]] = None, **kwargs: ~typing.Any)

  Base class for all supervisor strategies.

  add(*services: ServiceT) → None
  discard(*services: ServiceT) → None
  insert(index: int, service: ServiceT) → None
  property label: str

    Label used for graphs.

  logger: logging.Logger = <Logger mode.supervisors (WARNING)>
  async on_start() → None

    Service is starting.

  async on_stop() → None

    Service is being stopped/restarted.

  async restart_service(service: ServiceT) → None
  async restart_services(services: list[mode.types.services.ServiceT]) → None
  async run_until_complete() → None
  service_operational(service: ServiceT) → bool
  async start_service(service: ServiceT) → None
  async start_services(services: list[mode.types.services.ServiceT]) → None
  async stop_services(services: list[mode.types.services.ServiceT]) → None
  wakeup() → None

class mode.SupervisorStrategyT(*services: ServiceT, max_restarts: Union[timedelta, int, float, str] = 100.0, over: Union[timedelta, int, float, str] = 1.0, raises: Optional[Type[BaseException]] = None, replacement: Optional[Callable[[ServiceT, int], Awaitable[ServiceT]]] = None, **kwargs: Any)

  Base type for all supervisor strategies.

  abstract add(*services: ServiceT) → None
  abstract discard(*services: ServiceT) → None
  max_restarts: float
  over: float
  raises: Type[BaseException]
  abstract async restart_service(service: ServiceT) → None
  abstract service_operational(service: ServiceT) → bool
  abstract wakeup() → None

class mode.SyncSignal(*, name: Optional[str] = None, owner: Optional[Type] = None, loop: Optional[AbstractEventLoop] = None, default_sender: Optional[Any] = None, receivers: Optional[MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]] = None, filter_receivers: Optional[MutableMapping[Any, MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]]] = None)

  Signal that is synchronous (using regular `def` functions).

  clone(**kwargs: Any) → SyncSignalT
  send(*args: Any, **kwargs: Any) → None
  with_default_sender(sender: Any = None) → SyncSignalT

class mode.SyncSignalT(*, name: Optional[str] = None, owner: Optional[Type] = None, loop: Optional[AbstractEventLoop] = None, default_sender: Optional[Any] = None, receivers: Optional[MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]] = None, filter_receivers: Optional[MutableMapping[Any, MutableSet[Union[Callable[[], Union[Callable[[T, Any, BaseSignalT, Any], None], Callable[[T, Any, BaseSignalT, Any], Awaitable[None]]]], weakref[Union[Callable[[T, Any, mode.types.signals.BaseSignalT, Any], NoneType], Callable[[T, Any, mode.types.signals.BaseSignalT, Any], Awaitable[NoneType]]]]]]]] = None)

  Base class for all synchronous signals (using regular `def`).

  abstract clone(**kwargs: Any) → SyncSignalT
  abstract send(sender: T_contra, *args: Any, **kwargs: Any) → None
  abstract with_default_sender(sender: Any = None) → SyncSignalT

class mode.Worker(*services: ServiceT, debug: bool = False, quiet: bool = False, log_level: Severity = 20, log_file: str | os.PathLike | IO | None = None, log_handlers: Iterable[Handler] | None = None, redirect_stdouts: bool = True, redirect_stdouts_level: Severity = 30, stdout: IO | None = <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>, stderr: IO | None = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, override_logging: bool = True, logging_config: dict | None = None, console_port: int = 50101, blocking_timeout: Seconds = 10.0, daemon: bool = True, loop: asyncio.AbstractEventLoop | None = None, **kwargs: Any)

  Start mode service from the command-line.

  BLOCK_DETECTOR: ClassVar[str] = 'mode.debug:BlockingDetector'
  property blocking_detector: BlockingDetector
  blocking_timeout: Seconds
  carp(msg: str) → None

    Write warning to standard err.

  console_port: int
  debug: bool
  async default_on_first_start() → None
  execute_from_commandline() → NoReturn
  install_signal_handlers() → None
  log_file: str | os.PathLike | IO | None
  log_handlers: list[Handler]
  log_level: Severity | None
  logger: logging.Logger = <Logger mode.worker (WARNING)>
  logging_config: dict | None
  async maybe_start_blockdetection() → None
  async on_execute() → None
  async on_first_start() → None

    Service started for the first time in this process.

  on_init_dependencies() → Iterable[ServiceT]

    Return list of service dependencies for this service.

  on_setup_root_logger(logger: Logger, level: int) → None
  async on_started() → None

    Service has started.

  on_worker_shutdown() → None
  quiet: bool
  redirect_stdouts: bool
  redirect_stdouts_level: int
  say(msg: str) → None

    Write message to standard out.

  services: Iterable[ServiceT]
  stderr: IO
  stdout: IO
  stop_and_shutdown() → None

class mode.flight_recorder(logger: Logger, *, timeout: Union[timedelta, int, float, str], loop: Optional[AbstractEventLoop] = None)

  Flight Recorder context for use with `with` statement.

  This is a logging utility to log stuff only when something times out.

  For example if you have a background thread that is sometimes hanging:

      class RedisCache(mode.Service):

          @mode.timer(1.0)
          def _background_refresh(self) -> None:
              self._users = await self.redis_client.get(USER_KEY)
              self._posts = await self.redis_client.get(POSTS_KEY)

  You want to figure out on what line this is hanging, but logging all the time will provide way too much output, and will even change how fast the program runs and that can mask race conditions, so that they never happen.

  Use the flight recorder to save the logs and only log when it times out:

      logger = mode.get_logger(__name__)

      class RedisCache(mode.Service):

          @mode.timer(1.0)
          def _background_refresh(self) -> None:
              with mode.flight_recorder(logger, timeout=10.0) as on_timeout:
                  on_timeout.info(f'+redis_client.get({USER_KEY!r})')
                  await self.redis_client.get(USER_KEY)
                  on_timeout.info(f'-redis_client.get({USER_KEY!r})')

                  on_timeout.info(f'+redis_client.get({POSTS_KEY!r})')
                  await self.redis_client.get(POSTS_KEY)
                  on_timeout.info(f'-redis_client.get({POSTS_KEY!r})')

  If the body of this `with` statement completes before the timeout, the logs are forgotten about and never emitted – if it takes more than ten seconds to complete, we will see these messages in the log:

      [2018-04-19 09:43:55,877: WARNING]: Warning: Task timed out!
      [2018-04-19 09:43:55,878: WARNING]:
          Please make sure it is hanging before restarting.
      [2018-04-19 09:43:55,878: INFO]: [Flight Recorder-1]
          (started at Thu Apr 19 09:43:45 2018) Replaying logs...
      [2018-04-19 09:43:55,878: INFO]: [Flight Recorder-1]
          (Thu Apr 19 09:43:45 2018) +redis_client.get('user')
      [2018-04-19 09:43:55,878: INFO]: [Flight Recorder-1]
          (Thu Apr 19 09:43:49 2018) -redis_client.get('user')
      [2018-04-19 09:43:55,878: INFO]: [Flight Recorder-1]
          (Thu Apr 19 09:43:46 2018) +redis_client.get('posts')
      [2018-04-19 09:43:55,878: INFO]: [Flight Recorder-1] -End of log-

  Now we know this `redis_client.get` call can take too long to complete, and should consider adding a timeout to it.

  activate() → None
  blush() → None
  cancel() → None
  enabled_by: _asyncio.Task | None
  extra_context: dict[str, Any]
  flush_logs(ident: Optional[str] = None) → None
  log(severity: int, message: str, *args: Any, **kwargs: Any) → None
  logger: Logger
  loop: AbstractEventLoop
  started_at_date: str | None
  timeout: float
  wrap(severity: int, obj: Any) → Logwrapped
  wrap_debug(obj: Any) → Logwrapped
  wrap_error(obj: Any) → Logwrapped
  wrap_info(obj: Any) → Logwrapped
  wrap_warn(obj: Any) → Logwrapped

mode.get_logger(name: str) → Logger

  Get logger by name.

mode.label(s: Any) → str

  Return the name of an object as string.

mode.setup_logging(*, log_level: Optional[Union[int, str]] = None, log_file: Optional[Union[PathLike, str, IO]] = None, log_handlers: Optional[Iterable[Handler]] = None, logging_config: Optional[dict] = None) → int

  Configure logging subsystem.

mode.shortlabel(s: Any) → str

  Return the shortened name of an object as string.

mode.task(fun: Callable[[Any], Awaitable[None]]) → ServiceTask

  Decorate function to be used as background task.

  Example

      >>> class S(Service):
      ...
      ...     @Service.task
      ...     async def background_task(self):
      ...         while not self.should_stop:
      ...             await self.sleep(1.0)
      ...             print('Waking up')

mode.timer(interval: Union[timedelta, int, float, str], *, exec_first: bool = False, name: Optional[str] = None, max_drift_correction: float = 0.1) → Callable[[Callable], ServiceTask]

  Background timer executing every `n` seconds.

  Example

      >>> class S(Service):
      ...
      ...     @Service.timer(1.0)
      ...     async def background_timer(self):
      ...         print('Waking up')

mode.want_seconds(s: int | float | str | datetime.timedelta) → float
mode.want_seconds(s: int | float) → float
mode.want_seconds(s: int | float) → float
mode.want_seconds(s: str) → float
mode.want_seconds(s: timedelta) → float

  Convert `Seconds` to float.