Skip to content

Commit 7b45690

Browse files
authored
Merge pull request #939 from nolar/document-and-test-instance-and-class-methods
Document the limited usage of instance- & class-methods for handlers
2 parents a8a8701 + 4ad5674 commit 7b45690

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

docs/handlers.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ To register a handler for an event, use the ``@kopf.on`` decorator::
4949

5050
All available decorators are described below.
5151

52+
Kopf only supports simple functions and static methods as handlers.
53+
Class and instance methods are not supported.
54+
For explanation and rationale, see the discussion in `#849`__ (briefly:
55+
the semantics of handlers is vague when multiple instances exist or
56+
multiple sub-classes inherit from the class, thus inheriting the handlers).
57+
58+
__ https://github.com/nolar/kopf/issues/849
59+
60+
Would you still want to use classes for namespacing, register the handlers
61+
by using Kopf's decorators explicitly for specific instances/sub-classes thus
62+
resolving the mentioned vagueness and giving the meaning to ``self``/``cls``::
63+
64+
import kopf
65+
66+
class MyCls:
67+
def my_handler(self, spec, **kwargs):
68+
print(repr(self))
69+
70+
instance = MyCls()
71+
kopf.on.create('kopfexamples')(instance.my_handler)
72+
5273

5374
Event-watching handlers
5475
=======================

tests/registries/test_decorators.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,64 @@ def fn(**_):
441441
assert handlers[0].new is None
442442

443443

444+
def test_explicit_instance_methods(resource, cause_factory):
445+
registry = OperatorRegistry()
446+
cause = cause_factory(resource=resource, reason=Reason.CREATE)
447+
448+
class TestCls:
449+
def fn(self, **_):
450+
pass
451+
452+
obj1 = TestCls()
453+
kopf.on.create(*resource, registry=registry)(obj1.fn)
454+
455+
handlers = registry._changing.get_handlers(cause)
456+
assert len(handlers) == 1
457+
assert handlers[0].fn == obj1.fn # a bound method
458+
459+
460+
def test_explicit_subclass_methods(resource, cause_factory):
461+
registry = OperatorRegistry()
462+
cause = cause_factory(resource=resource, reason=Reason.CREATE)
463+
464+
class BaseCls:
465+
@classmethod
466+
def fn(cls, **_):
467+
pass
468+
469+
class TestCls(BaseCls):
470+
pass
471+
472+
kopf.on.create(*resource, registry=registry)(TestCls.fn)
473+
474+
handlers = registry._changing.get_handlers(cause)
475+
assert len(handlers) == 1
476+
assert handlers[0].fn != BaseCls.fn # an improperly bound method
477+
assert handlers[0].fn == TestCls.fn # a properly bound method
478+
479+
480+
def test_explicit_static_methods(resource, cause_factory):
481+
registry = OperatorRegistry()
482+
cause = cause_factory(resource=resource, reason=Reason.CREATE)
483+
484+
class BaseCls:
485+
@staticmethod
486+
def fn(cls, **_):
487+
pass
488+
489+
class TestCls(BaseCls):
490+
pass
491+
492+
obj = TestCls()
493+
kopf.on.create(*resource, registry=registry)(obj.fn)
494+
495+
handlers = registry._changing.get_handlers(cause)
496+
assert len(handlers) == 1
497+
assert handlers[0].fn == BaseCls.fn # an improperly bound method
498+
assert handlers[0].fn == TestCls.fn # a properly bound method
499+
assert handlers[0].fn == obj.fn # a properly bound method
500+
501+
444502
def test_subhandler_fails_with_no_parent_handler():
445503

446504
registry = ChangingRegistry()

0 commit comments

Comments
 (0)