async def _call_handler(handler: registries.Handler, *args, cause: 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 diffs.resolve( cause.old, handler.field, None) new = cause.new if handler.field is None else diffs.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)
def test_existing_key(): d = {'abc': {'def': {'hij': 'val'}}} r = resolve(d, ['abc', 'def', 'hij']) assert r == 'val'
def test_empty_path(): d = {'key': 'val'} r = resolve(d, []) assert r == d assert r is d
def test_nonmapping_key(): d = {'key': 'val'} with pytest.raises(TypeError): resolve(d, ['key', 'sub'])
def test_unexisting_key(): d = {'abc': {'def': {'hij': 'val'}}} with pytest.raises(KeyError): resolve(d, ['abc', 'def', 'xyz'])
def test_unexisting_key_with_default_value(): default = object() d = {'abc': {'def': {'hij': 'val'}}} r = resolve(d, ['abc', 'def', 'xyz'], default) assert r is default
def test_unexisting_key_with_default_none(): d = {'abc': {'def': {'hij': 'val'}}} r = resolve(d, ['abc', 'def', 'xyz'], None) assert r is None