Skip to content
This repository has been archived by the owner on Sep 14, 2020. It is now read-only.

Commit

Permalink
Merge pull request #345 from nolar/multi-callbacks
Browse files Browse the repository at this point in the history
Provide helpers to combine few callbacks for body-/meta-filters
  • Loading branch information
nolar authored Apr 8, 2020
2 parents fbc050d + c33c349 commit 698171a
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 1 deletion.
29 changes: 29 additions & 0 deletions docs/filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,32 @@ By arbitrary callbacks
annotations={'some-annotation': check_value})
def my_handler(spec, **_):
pass

Kopf provides few helpers to combine multiple callbacks into one
(the semantics is the same as for Python's built-in functions)::

import kopf

def whole_fn1(name, **_): return name.startswith('kopf-')
def whole_fn2(spec, **_): return spec.get('field') == 'value'
def value_fn1(value, **_): return value.startswith('some')
def value_fn2(value, **_): return value.endswith('label')

@kopf.on.create('zalando.org', 'v1', 'kopfexamples',
when=kopf.all_([whole_fn1, whole_fn2]),
labels={'somelabel': kopf.all_([value_fn1, value_fn2])})
def create_fn1(**_):
pass

@kopf.on.create('zalando.org', 'v1', 'kopfexamples',
when=kopf.any_([whole_fn1, whole_fn2]),
labels={'somelabel': kopf.any_([value_fn1, value_fn2])})
def create_fn2(**_):
pass

The following wrappers are available:

* `kopf.not_(fn)` -- the function must return ``False`` to pass the filters.
* `kopf.any_([...])` -- at least one of the functions must return ``True``.
* `kopf.all_([...])` -- all of the functions must return ``True``.
* `kopf.none_([...])` -- all of the functions must return ``False``.
10 changes: 10 additions & 0 deletions kopf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@
build_object_reference,
build_owner_reference,
)
from kopf.structs.callbacks import (
not_,
all_,
any_,
none_,
)
from kopf.structs.configuration import (
OperatorSettings,
)
Expand Down Expand Up @@ -162,6 +168,10 @@
'event', 'info', 'warn', 'exception',
'spawn_tasks', 'run_tasks', 'operator', 'run', 'create_tasks',
'adopt', 'label',
'not_',
'all_',
'any_',
'none_',
'get_default_lifecycle', 'set_default_lifecycle',
'build_object_reference', 'build_owner_reference',
'append_owner_reference', 'remove_owner_reference',
Expand Down
29 changes: 28 additions & 1 deletion kopf/structs/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
not so important for the codebase, they are moved to this separate module.
"""
import logging
from typing import NewType, Any, Union, Optional, Callable, Coroutine
from typing import NewType, Any, Collection, Union, Optional, Callable, Coroutine, TypeVar

from typing_extensions import Protocol

Expand Down Expand Up @@ -171,3 +171,30 @@ def __call__( # lgtm[py/similar-function]
logger: Union[logging.Logger, logging.LoggerAdapter],
**kwargs: Any,
) -> bool: ...


_FnT = TypeVar('_FnT', WhenFilterFn, MetaFilterFn)


def not_(fn: _FnT) -> _FnT:
def not_fn(*args: Any, **kwargs: Any) -> bool:
return not fn(*args, **kwargs)
return not_fn


def all_(fns: Collection[_FnT]) -> _FnT:
def all_fn(*args: Any, **kwargs: Any) -> bool:
return all(fn(*args, **kwargs) for fn in fns)
return all_fn


def any_(fns: Collection[_FnT]) -> _FnT:
def any_fn(*args: Any, **kwargs: Any) -> bool:
return any(fn(*args, **kwargs) for fn in fns)
return any_fn


def none_(fns: Collection[_FnT]) -> _FnT:
def none_fn(*args: Any, **kwargs: Any) -> bool:
return not any(fn(*args, **kwargs) for fn in fns)
return none_fn
101 changes: 101 additions & 0 deletions tests/test_filtering_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import kopf


def _never1(*_, **__):
return False


def _never2(*_, **__):
return False


def _always1(*_, **__):
return True


def _always2(*_, **__):
return True


def test_notfn_when_true():
combined = kopf.not_(_always1)
result = combined()
assert result is False


def test_notfn_when_false():
combined = kopf.not_(_never1)
result = combined()
assert result is True


def test_allfn_when_all_are_true():
combined = kopf.all_([_always1, _always2])
result = combined()
assert result is True


def test_allfn_when_one_is_false():
combined = kopf.all_([_always1, _never1])
result = combined()
assert result is False


def test_allfn_when_all_are_false():
combined = kopf.all_([_never1, _never2])
result = combined()
assert result is False


def test_allfn_when_no_functions():
combined = kopf.all_([])
result = combined()
assert result is True


def test_anyfn_when_all_are_true():
combined = kopf.any_([_always1, _always2])
result = combined()
assert result is True


def test_anyfn_when_one_is_false():
combined = kopf.any_([_always1, _never1])
result = combined()
assert result is True


def test_anyfn_when_all_are_false():
combined = kopf.any_([_never1, _never2])
result = combined()
assert result is False


def test_anyfn_when_no_functions():
combined = kopf.any_([])
result = combined()
assert result is False


def test_nonefn_when_all_are_true():
combined = kopf.none_([_always1, _always2])
result = combined()
assert result is False


def test_nonefn_when_one_is_false():
combined = kopf.none_([_always1, _never1])
result = combined()
assert result is False


def test_nonefn_when_all_are_false():
combined = kopf.none_([_never1, _never2])
result = combined()
assert result is True


def test_nonefn_when_no_functions():
combined = kopf.none_([])
result = combined()
assert result is True

0 comments on commit 698171a

Please sign in to comment.