async def _call_handler(handler: registries.Handler, *args, cause: causation.Cause, lifecycle: Callable, **kwargs): """ Invoke one handler only, according to the calling conventions. Specifically, calculate the handler-specific fields (e.g. field diffs). Ensure the global context for this asyncio task is set to the handler and its cause -- for proper population of the sub-handlers via the decorators (see `@kopf.on.this`). """ # For the field-handlers, the old/new/diff values must match the field, not the whole object. old = cause.old if handler.field is None else dicts.resolve( cause.old, handler.field, None) new = cause.new if handler.field is None else dicts.resolve( cause.new, handler.field, None) diff = cause.diff if handler.field is None else diffs.reduce( cause.diff, handler.field) cause = cause._replace(old=old, new=new, diff=diff) # Store the context of the current resource-object-event-handler, to be used in `@kopf.on.this`, # and maybe other places, and consumed in the recursive `execute()` calls for the children. # This replaces the multiple kwargs passing through the whole call stack (easy to forget). sublifecycle_token = sublifecycle_var.set(lifecycle) subregistry_token = subregistry_var.set( registries.SimpleRegistry(prefix=handler.id)) subexecuted_token = subexecuted_var.set(False) handler_token = handler_var.set(handler) cause_token = cause_var.set(cause) # And call it. If the sub-handlers are not called explicitly, run them implicitly # as if it was done inside of the handler (i.e. under try-finally block). try: result = await invocation.invoke( handler.fn, *args, cause=cause, **kwargs, ) if not subexecuted_var.get(): await execute() return result finally: # Reset the context to the parent's context, or to nothing (if already in a root handler). sublifecycle_var.reset(sublifecycle_token) subregistry_var.reset(subregistry_token) subexecuted_var.reset(subexecuted_token) handler_var.reset(handler_token) cause_var.reset(cause_token)
async def _call_handler( handler: registries.Handler, *args: Any, cause: causation.BaseCause, lifecycle: lifecycles.LifeCycleFn, **kwargs: Any, ) -> Any: """ Invoke one handler only, according to the calling conventions. Specifically, calculate the handler-specific fields (e.g. field diffs). Ensure the global context for this asyncio task is set to the handler and its cause -- for proper population of the sub-handlers via the decorators (see `@kopf.on.this`). """ # For the field-handlers, the old/new/diff values must match the field, not the whole object. if isinstance(cause, causation.StateChangingCause) and handler.field is not None: old = dicts.resolve(cause.old, handler.field, None, assume_empty=True) new = dicts.resolve(cause.new, handler.field, None, assume_empty=True) diff = diffs.reduce(cause.diff, handler.field) cause = causation.enrich_cause(cause=cause, old=old, new=new, diff=diff) # Store the context of the current resource-object-event-handler, to be used in `@kopf.on.this`, # and maybe other places, and consumed in the recursive `execute()` calls for the children. # This replaces the multiple kwargs passing through the whole call stack (easy to forget). with invocation.context([ (sublifecycle_var, lifecycle), (subregistry_var, registries.SimpleRegistry(prefix=handler.id)), (subexecuted_var, False), (handler_var, handler), (cause_var, cause), ]): # And call it. If the sub-handlers are not called explicitly, run them implicitly # as if it was done inside of the handler (i.e. under try-finally block). result = await invocation.invoke( handler.fn, *args, cause=cause, **kwargs, ) if not subexecuted_var.get() and isinstance( cause, causation.StateChangingCause): await execute() return result
async def execute( *, fns: Optional[Iterable[Callable]] = None, handlers: Optional[Iterable[registries.Handler]] = None, registry: Optional[registries.BaseRegistry] = None, lifecycle: Callable = None, cause: causation.Cause = None, ) -> None: """ Execute the handlers in an isolated lifecycle. This function is just a public wrapper for `execute` with multiple ways to specify the handlers: either as the raw functions, or as the pre-created handlers, or as a registry (as used in the object handling). If no explicit functions or handlers or registry are passed, the sub-handlers of the current handler are assumed, as accumulated in the per-handler registry with ``@kopf.on.this``. If the call to this method for the sub-handlers is not done explicitly in the handler, it is done implicitly after the handler is exited. One way or another, it is executed for the sub-handlers. """ # Restore the current context as set in the handler execution cycle. lifecycle = lifecycle if lifecycle is not None else sublifecycle_var.get() handler = handler_var.get(None) cause = cause if cause is not None else cause_var.get() # Validate the inputs; the function signatures cannot put these kind of restrictions, so we do. if len([v for v in [fns, handlers, registry] if v is not None]) > 1: raise TypeError( "Only one of the fns, handlers, registry can be passed. Got more.") elif fns is not None and isinstance(fns, collections.Mapping): registry = registries.SimpleRegistry( prefix=handler.id if handler else None) for id, fn in fns.items(): registry.register(fn=fn, id=id) elif fns is not None and isinstance(fns, collections.Iterable): registry = registries.SimpleRegistry( prefix=handler.id if handler else None) for fn in fns: registry.register(fn=fn) elif fns is not None: raise ValueError( f"fns must be a mapping or an iterable, got {fns.__class__}.") elif handlers is not None: registry = registries.SimpleRegistry( prefix=handler.id if handler else None) for handler in handlers: registry.append(handler=handler) # Use the registry as is; assume that the caller knows what they do. elif registry is not None: pass # Prevent double implicit execution. elif subexecuted_var.get(): return # If no explicit args were passed, implicitly use the accumulated handlers from `@kopf.on.this`. else: subexecuted_var.set(True) registry = subregistry_var.get() # Execute the real handlers (all or few or one of them, as per the lifecycle). # Raises `HandlerChildrenRetry` if the execute should be continued on the next iteration. await _execute( lifecycle=lifecycle, handlers=registry.get_cause_handlers(cause=cause), cause=cause, )