예제 #1
0
def _matches_field_values(
    handler: handlers.ResourceHandler,
    cause: causation.ResourceCause,
    kwargs: MutableMapping[str, Any],
) -> bool:
    if not handler.field:
        return True

    if not kwargs:
        kwargs.update(invocation.build_kwargs(cause=cause))

    absent = _UNSET.token  # or any other identifyable object
    if isinstance(cause, causation.ResourceChangingCause):
        # For on.update/on.field, so as for on.create/resume/delete for uniformity and simplicity:
        old = dicts.resolve(cause.old, handler.field, absent)
        new = dicts.resolve(cause.new, handler.field, absent)
        values = [
            new, old
        ]  # keep "new" first, to avoid "old" callbacks if "new" works.
    else:
        # For event-watching, timers/daemons (could also work for on.create/resume/delete):
        val = dicts.resolve(cause.body, handler.field, absent)
        values = [val]
    return ((handler.value is None and any(value is not absent
                                           for value in values))
            or (handler.value is filters.PRESENT and any(value is not absent
                                                         for value in values))
            or (handler.value is filters.ABSENT and any(value is absent
                                                        for value in values))
            or (callable(handler.value)
                and any(handler.value(value, **kwargs) for value in values))
            or (any(handler.value == value for value in values)))
예제 #2
0
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)
예제 #3
0
def reduce_iter(d: Diff, path: dicts.FieldPath) -> Iterator[DiffItem]:
    for op, field, old, new in d:

        # As-is diff (i.e. a root field).
        if not path:
            yield DiffItem(op, tuple(field), old, new)

        # The diff-field is longer than the path: get "spec.struct" when "spec.struct.field" is set.
        # Retranslate the diff with the field prefix shrinked.
        elif tuple(field[:len(path)]) == tuple(path):
            yield DiffItem(op, tuple(field[len(path):]), old, new)

        # The diff-field is shorter than the path: get "spec.struct" when "spec={...}" is added.
        # Generate a new diff, with new ops, for the resolved sub-field.
        elif tuple(field) == tuple(path[:len(field)]):
            tail = path[len(field):]
            old_tail = dicts.resolve(old,
                                     tail,
                                     default=None,
                                     assume_empty=True)
            new_tail = dicts.resolve(new,
                                     tail,
                                     default=None,
                                     assume_empty=True)
            yield from diff_iter(old_tail, new_tail)
예제 #4
0
def _matches_field_changes(
    handler: handlers.ResourceHandler,
    cause: causation.ResourceCause,
    kwargs: MutableMapping[str, Any],
) -> bool:
    if not isinstance(handler, handlers.ResourceChangingHandler):
        return True
    if not isinstance(cause, causation.ResourceChangingCause):
        return True
    if not handler.field:
        return True

    if not kwargs:
        kwargs.update(invocation.build_kwargs(cause=cause))

    absent = _UNSET.token  # or any other identifyable object
    old = dicts.resolve(cause.old, handler.field, absent)
    new = dicts.resolve(cause.new, handler.field, absent)
    return ((
        not handler.field_needs_change
        or old != new  # ... or there IS a change.
    ) and ((handler.old is None) or
           (handler.old is filters.ABSENT and old is absent) or
           (handler.old is filters.PRESENT and old is not absent) or
           (callable(handler.old) and handler.old(old, **kwargs)) or
           (handler.old == old))
            and ((handler.new is None) or
                 (handler.new is filters.ABSENT and new is absent) or
                 (handler.new is filters.PRESENT and new is not absent) or
                 (callable(handler.new) and handler.new(new, **kwargs)) or
                 (handler.new == new)))
예제 #5
0
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)
예제 #6
0
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
예제 #7
0
파일: progress.py 프로젝트: silveryfu/kopf
 def purge(
     self,
     *,
     key: handlers.HandlerId,
     body: bodies.Body,
     patch: patches.Patch,
 ) -> None:
     absent = object()
     key_field = self.field + (key, )
     body_value = dicts.resolve(body, key_field, absent)
     patch_value = dicts.resolve(patch, key_field, absent)
     if body_value is not absent:
         dicts.ensure(patch, key_field, None)
     elif patch_value is not absent:
         dicts.remove(patch, key_field)
예제 #8
0
파일: progress.py 프로젝트: silveryfu/kopf
 def purge(
     self,
     *,
     key: handlers.HandlerId,
     body: bodies.Body,
     patch: patches.Patch,
 ) -> None:
     absent = object()
     for full_key in self.make_keys(key, body=body):
         key_field = ['metadata', 'annotations', full_key]
         body_value = dicts.resolve(body, key_field, absent)
         patch_value = dicts.resolve(patch, key_field, absent)
         if body_value is not absent:
             dicts.ensure(patch, key_field, None)
         elif patch_value is not absent:
             dicts.remove(patch, key_field)
예제 #9
0
 def fetch(
         self,
         *,
         body: bodies.Body,
 ) -> Optional[bodies.BodyEssence]:
     encoded: Optional[str] = dicts.resolve(body, self.field, None)
     essence: Optional[bodies.BodyEssence] = json.loads(encoded) if encoded is not None else None
     return essence
예제 #10
0
 def fetch(
     self,
     *,
     key: handlers.HandlerId,
     body: bodies.Body,
 ) -> Optional[ProgressRecord]:
     container: Mapping[handlers.HandlerId, ProgressRecord]
     container = dicts.resolve(body, self.field, {})
     return container.get(key, None)
예제 #11
0
 def purge(
     self,
     *,
     key: handlers.HandlerId,
     body: bodies.Body,
     patch: patches.Patch,
 ) -> None:
     absent = object()
     full_key = self.make_key(key)
     key_field = ['metadata', 'annotations', full_key]
     body_value = dicts.resolve(body, key_field, absent, assume_empty=True)
     patch_value = dicts.resolve(patch,
                                 key_field,
                                 absent,
                                 assume_empty=True)
     if body_value is not absent:
         dicts.ensure(patch, key_field, None)
     elif patch_value is not absent:
         dicts.remove(patch, key_field)
예제 #12
0
 def touch(
     self,
     *,
     body: bodies.Body,
     patch: patches.Patch,
     value: Optional[str],
 ) -> None:
     key_field = self.touch_field
     body_value = dicts.resolve(body, key_field, None, assume_empty=True)
     if body_value != value:  # also covers absent-vs-None cases.
         dicts.ensure(patch, key_field, value)
예제 #13
0
 def fetch(
     self,
     *,
     key: handlers.HandlerId,
     body: bodies.Body,
 ) -> Optional[ProgressRecord]:
     full_key = self.make_key(key)
     key_field = ['metadata', 'annotations', full_key]
     encoded = dicts.resolve(body, key_field, None, assume_empty=True)
     decoded = json.loads(encoded) if encoded is not None else None
     return cast(Optional[ProgressRecord], decoded)
예제 #14
0
 def touch(
     self,
     *,
     body: bodies.Body,
     patch: patches.Patch,
     value: Optional[str],
 ) -> None:
     full_key = self.make_key(self.touch_key)
     key_field = ['metadata', 'annotations', full_key]
     body_value = dicts.resolve(body, key_field, None, assume_empty=True)
     if body_value != value:  # also covers absent-vs-None cases.
         dicts.ensure(patch, key_field, value)
예제 #15
0
파일: progress.py 프로젝트: silveryfu/kopf
 def touch(
     self,
     *,
     body: bodies.Body,
     patch: patches.Patch,
     value: Optional[str],
 ) -> None:
     for full_key in self.make_keys(self.touch_key, body=body):
         key_field = ['metadata', 'annotations', full_key]
         body_value = dicts.resolve(body, key_field, None)
         if body_value != value:  # also covers absent-vs-None cases.
             dicts.ensure(patch, key_field, value)
             self._store_marker(prefix=self.prefix, patch=patch, body=body)
예제 #16
0
파일: progress.py 프로젝트: silveryfu/kopf
 def fetch(
     self,
     *,
     key: handlers.HandlerId,
     body: bodies.Body,
 ) -> Optional[ProgressRecord]:
     for full_key in self.make_keys(key, body=body):
         key_field = ['metadata', 'annotations', full_key]
         encoded = dicts.resolve(body, key_field, None)
         decoded = json.loads(encoded) if encoded is not None else None
         if decoded is not None:
             return cast(ProgressRecord, decoded)
     return None
예제 #17
0
def test_empty_path():
    d = {'key': 'val'}
    r = resolve(d, [])
    assert r == d
    assert r is d
예제 #18
0
def test_existing_key():
    d = {'abc': {'def': {'hij': 'val'}}}
    r = resolve(d, ['abc', 'def', 'hij'])
    assert r == 'val'
예제 #19
0
def test_nonmapping_with_default():
    d = {'key': 'val'}
    r = resolve(d, ['key', 'sub'], default)
    assert r is default
예제 #20
0
def test_nonmapping_with_no_default():
    d = {'key': 'val'}
    with pytest.raises(TypeError):
        resolve(d, ['key', 'sub'])
예제 #21
0
def test_inexistent_key_with_default(key):
    d = {'abc': {'def': {'hij': 'val'}}}
    r = resolve(d, key, default)
    assert r is default
예제 #22
0
def test_inexistent_key_with_no_default(key):
    d = {'abc': {'def': {'hij': 'val'}}}
    with pytest.raises(KeyError):
        resolve(d, key)
예제 #23
0
def test_unexisting_key_with_no_default():
    d = {'abc': {'def': {'hij': 'val'}}}
    with pytest.raises(KeyError):
        resolve(d, ['abc', 'def', 'xyz'])
예제 #24
0
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
예제 #25
0
def test_unexisting_key_with_default_none():
    d = {'abc': {'def': {'hij': 'val'}}}
    r = resolve(d, ['abc', 'def', 'xyz'], None)
    assert r is None
예제 #26
0
def test_existent_key_with_default():
    d = {'abc': {'def': {'hij': 'val'}}}
    r = resolve(d, ['abc', 'def', 'hij'], default)
    assert r == 'val'
예제 #27
0
def test_none_is_treated_as_a_regular_default_value():
    d = {'abc': {'def': {'hij': 'val'}}}
    r = resolve(d, ['abc', 'def', 'xyz'], None)
    assert r is None