async def invoke_handler( handler: handlers_.BaseHandler, *args: Any, cause: causation.BaseCause, settings: configuration.OperatorSettings, lifecycle: Optional[lifecycles.LifeCycleFn], subrefs: Set[handlers_.HandlerId], **kwargs: Any, ) -> Optional[callbacks.Result]: """ 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.subhandler`). """ # For the field-handlers, the old/new/diff values must match the field, not the whole object. if (True and # for readable indenting isinstance(cause, causation.ResourceChangingCause) and isinstance(handler, handlers_.ResourceHandler) and handler.field is not None): old = dicts.resolve(cause.old, handler.field, None) new = dicts.resolve(cause.new, handler.field, None) 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 handler, to be used in `@kopf.subhandler`, # 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.ResourceChangingRegistry()), (subsettings_var, settings), (subexecuted_var, False), (subrefs_var, list(subrefs_var.get([])) + [subrefs]), (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, settings=settings, cause=cause, **kwargs, ) if not subexecuted_var.get() and isinstance( cause, causation.ResourceChangingCause): await execute() # Since we know that we invoked the handler, we cast "any" result to a handler result. return callbacks.Result(result)
async def execute( *, fns: Optional[Iterable[invocation.Invokable]] = None, handlers: Optional[Iterable[handlers_.ResourceHandler]] = None, registry: Optional[registries.ResourceChangingRegistry] = None, lifecycle: Optional[lifecycles.LifeCycleFn] = None, cause: Optional[causation.BaseCause] = 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() cause = cause if cause is not None else cause_var.get() parent_handler: handlers_.BaseHandler = handler_var.get() parent_prefix = parent_handler.id if parent_handler is not None else None # 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.abc.Mapping): subregistry = registries.ResourceChangingRegistry(prefix=parent_prefix) for id, fn in fns.items(): subregistry.register(fn=fn, id=id) elif fns is not None and isinstance(fns, collections.abc.Iterable): subregistry = registries.ResourceChangingRegistry(prefix=parent_prefix) for fn in fns: subregistry.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: subregistry = registries.ResourceChangingRegistry(prefix=parent_prefix) for handler in handlers: subregistry.append(handler=handler) # Use the registry as is; assume that the caller knows what they do. elif registry is not None: subregistry = registry # 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) subregistry = subregistry_var.get() # The sub-handlers are only for upper-level causes, not for lower-level events. if not isinstance(cause, causation.ResourceChangingCause): raise RuntimeError( "Sub-handlers of event-handlers are not supported and have " "no practical use (there are no retries or state tracking).") # Execute the real handlers (all or few or one of them, as per the lifecycle). subhandlers = subregistry.get_handlers(cause=cause) state = states.State.from_body(body=cause.body, handlers=subhandlers) outcomes = await execute_handlers_once( lifecycle=lifecycle, handlers=subhandlers, cause=cause, state=state, ) state = state.with_outcomes(outcomes) state.store(patch=cause.patch) states.deliver_results(outcomes=outcomes, patch=cause.patch) # Escalate `HandlerChildrenRetry` if the execute should be continued on the next iteration. if not state.done: raise HandlerChildrenRetry(delay=state.delay)
async def execute( *, fns: Optional[Iterable[callbacks.ResourceChangingFn]] = None, handlers: Optional[Iterable[handlers_.ResourceChangingHandler]] = None, registry: Optional[registries.ResourceChangingRegistry] = None, lifecycle: Optional[lifecycles.LifeCycleFn] = None, cause: Optional[causation.BaseCause] = 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.subhandler``. 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() lifecycle = lifecycle if lifecycle is not None else lifecycles.get_default_lifecycle( ) cause = cause if cause is not None else cause_var.get() parent_handler: handlers_.BaseHandler = handler_var.get() parent_prefix = parent_handler.id if parent_handler is not None else None # 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.abc.Mapping): subregistry = registries.ResourceChangingRegistry() for id, fn in fns.items(): real_id = registries.generate_id(fn=fn, id=id, prefix=parent_prefix) handler = handlers_.ResourceChangingHandler( fn=fn, id=real_id, errors=None, timeout=None, retries=None, backoff=None, cooldown=None, selector=None, labels=None, annotations=None, when=None, initial=None, deleted=None, requires_finalizer=None, reason=None, field=None, value=None, old=None, new=None, field_needs_change=None, ) subregistry.append(handler) elif fns is not None and isinstance(fns, collections.abc.Iterable): subregistry = registries.ResourceChangingRegistry() for fn in fns: real_id = registries.generate_id(fn=fn, id=None, prefix=parent_prefix) handler = handlers_.ResourceChangingHandler( fn=fn, id=real_id, errors=None, timeout=None, retries=None, backoff=None, cooldown=None, selector=None, labels=None, annotations=None, when=None, initial=None, deleted=None, requires_finalizer=None, reason=None, field=None, value=None, old=None, new=None, field_needs_change=None, ) subregistry.append(handler) elif fns is not None: raise ValueError( f"fns must be a mapping or an iterable, got {fns.__class__}.") elif handlers is not None: subregistry = registries.ResourceChangingRegistry() for handler in handlers: subregistry.append(handler) # Use the registry as is; assume that the caller knows what they do. elif registry is not None: subregistry = registry # Prevent double implicit execution. elif subexecuted_var.get(): return # If no explicit args were passed, use the accumulated handlers from `@kopf.subhandler`. else: subexecuted_var.set(True) subregistry = subregistry_var.get() # The sub-handlers are only for upper-level causes, not for lower-level events. if not isinstance(cause, causation.ResourceChangingCause): raise RuntimeError( "Sub-handlers of event-handlers are not supported and have " "no practical use (there are no retries or state tracking).") # Execute the real handlers (all or few or one of them, as per the lifecycle). settings: configuration.OperatorSettings = subsettings_var.get() owned_handlers = subregistry.get_resource_handlers(resource=cause.resource) cause_handlers = subregistry.get_handlers(cause=cause) storage = settings.persistence.progress_storage state = states.State.from_storage(body=cause.body, storage=storage, handlers=owned_handlers) state = state.with_purpose(cause.reason).with_handlers(cause_handlers) outcomes = await execute_handlers_once( lifecycle=lifecycle, settings=settings, handlers=cause_handlers, cause=cause, state=state, ) state = state.with_outcomes(outcomes) state.store(body=cause.body, patch=cause.patch, storage=storage) states.deliver_results(outcomes=outcomes, patch=cause.patch) # Enrich all parents with references to sub-handlers of any level deep (sub-sub-handlers, etc). # There is at least one container, as this function can be called only from a handler. subrefs_containers: Iterable[Set[handlers_.HandlerId]] = subrefs_var.get() for key in state: for subrefs_container in subrefs_containers: subrefs_container.add(key) # Escalate `HandlerChildrenRetry` if the execute should be continued on the next iteration. if not state.done: raise HandlerChildrenRetry(delay=state.delay)