Skip to content

Commit

Permalink
Document the limited usage of instance- & class-methods for handlers
Browse files Browse the repository at this point in the history
Signed-off-by: Sergey Vasilyev <nolar@nolar.info>
  • Loading branch information
nolar committed Jul 24, 2022
1 parent a8a8701 commit 4ad5674
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
21 changes: 21 additions & 0 deletions docs/handlers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ To register a handler for an event, use the ``@kopf.on`` decorator::

All available decorators are described below.

Kopf only supports simple functions and static methods as handlers.
Class and instance methods are not supported.
For explanation and rationale, see the discussion in `#849`__ (briefly:
the semantics of handlers is vague when multiple instances exist or
multiple sub-classes inherit from the class, thus inheriting the handlers).

__ https://github.com/nolar/kopf/issues/849

Would you still want to use classes for namespacing, register the handlers
by using Kopf's decorators explicitly for specific instances/sub-classes thus
resolving the mentioned vagueness and giving the meaning to ``self``/``cls``::

import kopf

class MyCls:
def my_handler(self, spec, **kwargs):
print(repr(self))

instance = MyCls()
kopf.on.create('kopfexamples')(instance.my_handler)


Event-watching handlers
=======================
Expand Down
58 changes: 58 additions & 0 deletions tests/registries/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,64 @@ def fn(**_):
assert handlers[0].new is None


def test_explicit_instance_methods(resource, cause_factory):
registry = OperatorRegistry()
cause = cause_factory(resource=resource, reason=Reason.CREATE)

class TestCls:
def fn(self, **_):
pass

obj1 = TestCls()
kopf.on.create(*resource, registry=registry)(obj1.fn)

handlers = registry._changing.get_handlers(cause)
assert len(handlers) == 1
assert handlers[0].fn == obj1.fn # a bound method


def test_explicit_subclass_methods(resource, cause_factory):
registry = OperatorRegistry()
cause = cause_factory(resource=resource, reason=Reason.CREATE)

class BaseCls:
@classmethod
def fn(cls, **_):
pass

class TestCls(BaseCls):
pass

kopf.on.create(*resource, registry=registry)(TestCls.fn)

handlers = registry._changing.get_handlers(cause)
assert len(handlers) == 1
assert handlers[0].fn != BaseCls.fn # an improperly bound method
assert handlers[0].fn == TestCls.fn # a properly bound method


def test_explicit_static_methods(resource, cause_factory):
registry = OperatorRegistry()
cause = cause_factory(resource=resource, reason=Reason.CREATE)

class BaseCls:
@staticmethod
def fn(cls, **_):
pass

class TestCls(BaseCls):
pass

obj = TestCls()
kopf.on.create(*resource, registry=registry)(obj.fn)

handlers = registry._changing.get_handlers(cause)
assert len(handlers) == 1
assert handlers[0].fn == BaseCls.fn # an improperly bound method
assert handlers[0].fn == TestCls.fn # a properly bound method
assert handlers[0].fn == obj.fn # a properly bound method


def test_subhandler_fails_with_no_parent_handler():

registry = ChangingRegistry()
Expand Down

0 comments on commit 4ad5674

Please sign in to comment.