# How to Use Class Methods and Static Methods as Event Handlers in Bubus

> Learn how to use class methods and static methods as event handlers in Bubus. Discover how Bubus automatically detects handler protocols and passes class or treats static methods as functions.

- Repository: [Browser Use/bubus](https://github.com/browser-use/bubus)
- Tags: how-to-guide
- Published: 2026-02-26

---

**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`](https://github.com/browser-use/bubus/blob/main/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`](https://github.com/browser-use/bubus/blob/main/bubus/models.py):

- **`EventHandlerClassMethod`** (lines 105-113): Defines the signature for synchronous class methods as `def __call__(self, cls: type[Any], event: T, /) -> Any`
- **`AsyncEventHandlerClassMethod`** (lines 116-124): Defines the signature for asynchronous class methods as `async 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`](https://github.com/browser-use/bubus/blob/main/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`](https://github.com/browser-use/bubus/blob/main/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:

1. An unbound class method (expects `cls` as first argument)
2. A static method or regular function (expects only the event)
3. 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()`:

```python
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:

```python
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:

```python
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:

```python
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`](https://github.com/browser-use/bubus/blob/main/bubus/service.py), the method uses overloaded type signatures to distinguish between:

- **Class methods**: Identified by the `EventHandlerClassMethod` protocol requiring a `cls` parameter
- **Static methods**: Fall through to the standard function handler overloads since they lack the `cls` binding

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 `EventHandlerClassMethod` or `AsyncEventHandlerClassMethod` protocols and receive the class (`cls`) as their first argument when invoked.
- **Static methods** are treated as standard function handlers using `EventHandlerFunc` protocols and receive only the event instance.
- Register both types using `EventBus.on(EventType, HandlerClass.method_name)` without special syntax.
- The implementation in [`bubus/service.py`](https://github.com/browser-use/bubus/blob/main/bubus/service.py) automatically 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`](https://github.com/browser-use/bubus/blob/main/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`](https://github.com/browser-use/bubus/blob/main/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`](https://github.com/browser-use/bubus/blob/main/tests/test_eventbus.py) demonstrates this pattern for validation.