def test_existent_path_selects_relevant_ops(): result = reduce(DIFF, ['key2']) assert result == ( ('change', (), 'old2', 'new2'), ('add', ('suba', ), 'olda', 'newa'), ('remove', ('subb', ), 'oldb', 'newb'), )
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)
def _matches_field( handler: handlers.ResourceHandler, cause: causation.ResourceCause, ignore_fields: bool = False, ) -> bool: return (ignore_fields or not isinstance(handler, handlers.ResourceChangingHandler) or not handler.field or (isinstance(cause, causation.ResourceChangingCause) and bool(diffs.reduce(cause.diff, handler.field))))
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.ResourceHandler, *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.ResourceChangingCause) 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.ResourceRegistry(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.ResourceChangingCause): await execute() return result
def test_overly_specific_path_dives_into_dicts_for_change(): result = reduce(DIFF, ['key4', 'subc']) assert result == (('change', (), 'oldc', 'newc'), )
def test_overly_specific_path_dives_into_dicts_for_removal(): result = reduce(DIFF, ['key4', 'suba']) assert result == (('remove', (), 'olda', None), )
def test_overly_specific_path_dives_into_dicts_for_addition(): result = reduce(DIFF, ['key4', 'subb']) assert result == (('add', (), None, 'newb'), )
def test_nonexistent_path_selects_nothing(): result = reduce(DIFF, ['nonexistent-key']) assert result == ()
def test_empty_path_selects_all_ops(): result = reduce(DIFF, []) assert result == DIFF
def test_type_ignored_for_inputs_but_is_tuple_for_output(diff, path): result = reduce(diff, path) assert result == (('op', (), 'old', 'new'), )
def test_nonexistent_path_selects_nothing(path): result = reduce(DIFF, path) assert result == ()