class_docstr: A middleware that wraps another execution engine.
  It can be used to transform the submitted models before passing to the underlying engine.
  
  Middlewares sits between a strategy and a engine.
  There could be multiple middlewares chained.
  Some middlewares logically belong to the strategy side, for example model filters and early stopper.
  Others logically belong to the engine side, for example CSE and benchmarking.
  This class is designed mainly for the engine side.
  Strategy side should inherit another dedicated superclass.
  
  Implementing a middleware is similar to implementing an engine,
  but with the option of leveraging the ability of the underlying wrapped engine.
  Apart from the methods that would otherwise raise NotImplementedError if not implemented,
  we recommend override :meth:`set_engine` and :meth:`register_model_event_callback`.
  In :meth:`set_engine`, the middleware registers some callbacks by itself on the underlying engine,
  while in :meth:`register_model_event_callback`, the middleware decides what to do with the callbacks from the outside.
  There are basically two approaches to handle the callbacks:
  
  1. Register the callbacks directly on the underlying engine.
     Since callbacks in :meth:`set_engine` are registered before the callbacks from the outside,
     they can intercept the events and manipulates/stops them when needed.
  2. Keep the callbacks to itself.
     Register callbacks written by the middleware itself to the underlying engine,
     which creates brand new events and uses :meth:`dispatch_model_event` to invoke the callbacks from the outside.
  
  Some other (hacky) approaches might not be possible (e.g., wrap the callbacks with a closure).
  But they are not recommended.
  
  Middleware should be responsible for unregistering the callbacks at :meth:`shutdown`.
  
  Parameters
  ----------
  engine
      The underlying execution engine.
code: def __init__(self, engine: ExecutionEngine | None = None) -> None:
      super().__init__()
      self._engine: ExecutionEngine | None = None
      if engine is not None:
          self.set_engine(engine)
  
imports: from __future__ import annotations
  import logging
  import time
  from collections import defaultdict
  from typing import Any, Iterable, NewType, Callable, Type, overload
  from nni.nas.space import ExecutableModelSpace, ModelStatus
  from .event import ModelEvent, ModelEventType, FinalMetricEvent, IntermediateMetricEvent, TrainingEndEvent
label_desc: Use image node_id 1 to create a new Middleware object from inherited base classes: ExecutionEngine with example: obj = Middleware(engine)