How to Use Class Methods and Static Methods as Event Handlers in Bubus
Bubus accepts class methods and static methods as event handlers through its EventBus.on() method, which uses type inspection to automatically detect the correct handler protocol and pass the class (cls) for class methods while treating static methods as standard functions.
The browser-use/bubus library provides a flexible event bus implementation that supports multiple handler types beyond simple functions. When building event-driven applications with bubus, you can register class methods and static methods as event handlers using the same EventBus.on() API used for regular functions.
Understanding Handler Protocols in Bubus
Bubus defines explicit protocols in bubus/models.py that specify the expected signatures for different handler types. These protocols enable static type checking and runtime dispatch.
Class Method Protocols
For class methods, bubus provides two distinct protocols in bubus/models.py:
EventHandlerClassMethod(lines 105-113): Defines the signature for synchronous class methods asdef __call__(self, cls: type[Any], event: T, /) -> AnyAsyncEventHandlerClassMethod(lines 116-124): Defines the signature for asynchronous class methods asasync def __call__(self, cls: type[Any], event: T, /) -> Any
Static Method Handling
Static methods in bubus are treated as standard function handlers. The library does not distinguish between a @staticmethod and a module-level function. Both use the EventHandlerFunc or AsyncEventHandlerFunc protocols defined in bubus/models.py, which expect signatures of (event: T) -> Any or async (event: T) -> Any respectively.
Registering Class Methods and Static Methods with EventBus.on()
The EventBus.on() method in bubus/service.py (lines 394-416) uses method overloading to accept different handler types. When you call bus.on(EventType, handler), bubus inspects the callable to determine whether it is:
- An unbound class method (expects
clsas first argument) - A static method or regular function (expects only the event)
- An async variant of either
The library automatically selects the appropriate overload and, for class methods, ensures the class is passed as the first argument when the event is dispatched.
Practical Code Examples
Sync Class Method Handlers
To register a synchronous class method, pass the unbound method reference to EventBus.on():
from bubus import EventBus, BaseEvent
class UserEvent(BaseEvent):
action: str
class EventProcessor:
@classmethod
def handle_event(cls, event: UserEvent) -> str:
return f"{cls.__name__} processed {event.action}"
# Setup
bus = EventBus()
bus.on(UserEvent, EventProcessor.handle_event)
# The handler receives EventProcessor as `cls` and the event instance
Static Method Handlers
Static methods work identically to regular functions. They receive only the event argument:
class EventLogger:
@staticmethod
def log_event(event: UserEvent) -> None:
print(f"Logging: {event.action}")
# Register exactly like a function
bus.on(UserEvent, EventLogger.log_event)
Async Class Method Handlers
For asynchronous class methods, use async def and bubus will automatically await the result:
class AsyncProcessor:
@classmethod
async def async_handle(cls, event: UserEvent) -> str:
await asyncio.sleep(0.01)
return f"{cls.__name__} async processed {event.action}"
bus.on(UserEvent, AsyncProcessor.async_handle)
Complete Working Example
The following demonstrates all handler types working together:
import asyncio
from bubus import EventBus, BaseEvent
class UserActionEvent(BaseEvent):
action: str
user_id: str
class EventProcessor:
def __init__(self, name: str):
self.name = name
def instance_handler(self, event: UserActionEvent) -> dict:
return {"processor": self.name, "action": event.action}
@classmethod
def class_method_handler(cls, event: UserActionEvent) -> str:
return f"{cls.__name__} handled {event.action!r}"
@staticmethod
def static_method_handler(event: UserActionEvent) -> str:
return f"static handled {event.action!r}"
async def main():
bus = EventBus()
processor = EventProcessor("MyProcessor")
# Register all handler types
bus.on(UserActionEvent, processor.instance_handler)
bus.on(UserActionEvent, EventProcessor.class_method_handler)
bus.on(UserActionEvent, EventProcessor.static_method_handler)
# Dispatch
event = UserActionEvent(action="login", user_id="alice")
completed = await bus.dispatch(event)
for result in await completed.event_results_list():
print(result)
asyncio.run(main())
How Bubus Detects Handler Types
When EventBus.on() is invoked, bubus performs type inspection to select the correct execution path. According to the implementation in bubus/service.py, the method uses overloaded type signatures to distinguish between:
- Class methods: Identified by the
EventHandlerClassMethodprotocol requiring aclsparameter - Static methods: Fall through to the standard function handler overloads since they lack the
clsbinding
This design allows bubus to support Python's descriptor protocol transparently. When a class method is registered, bubus stores the unbound function and ensures the class is passed as the first argument during dispatch, while static methods are invoked directly with only the event argument.
Summary
- Class methods in bubus use the
EventHandlerClassMethodorAsyncEventHandlerClassMethodprotocols and receive the class (cls) as their first argument when invoked. - Static methods are treated as standard function handlers using
EventHandlerFuncprotocols and receive only the event instance. - Register both types using
EventBus.on(EventType, HandlerClass.method_name)without special syntax. - The implementation in
bubus/service.pyautomatically detects the handler type through method overloading and protocol matching. - Both synchronous and asynchronous variants are fully supported for class and static methods.
Frequently Asked Questions
Can I use instance methods as event handlers in bubus?
Yes, instance methods work as event handlers in bubus. When you register an instance method such as bus.on(EventType, my_instance.method_name), bubus stores the bound method and invokes it with self already bound, passing only the event as the argument. This follows the standard EventHandlerFunc protocol since the bound method signature effectively becomes (event) -> Any.
What is the difference between class method and static method handlers in bubus?
The primary difference lies in what arguments the handler receives. Class method handlers receive the class itself (cls) as the first argument followed by the event, allowing access to class-level attributes and methods. Static method handlers receive only the event instance, behaving exactly like a module-level function. In bubus/models.py, these are distinguished by the EventHandlerClassMethod protocol (requiring cls) versus the standard EventHandlerFunc protocol.
Does bubus support async static method handlers?
Yes, bubus fully supports asynchronous static methods. When you decorate a method with both @staticmethod and async def, bubus detects it as an AsyncEventHandlerFunc according to the protocol defined in bubus/models.py. The event bus will automatically await the coroutine when dispatching events, collecting the result alongside synchronous handlers.
How do I test class method handlers in bubus?
Testing class method handlers follows the same pattern as testing regular handlers. Instantiate an EventBus, register your class method using bus.on(EventType, MyClass.my_method), then dispatch an event using await bus.dispatch(event). You can then inspect completed.event_results_list() to verify the class method received the correct cls argument and returned the expected value. The library's own test suite in tests/test_eventbus.py demonstrates this pattern for validation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →