I am implementing a message processor class. I'd like to use decorators to determine which class method should be used for processing a particular command type. Flask does something very similar to this with their @app.route('/urlpath') decorator.
I can't figure out a way to actually register the function as a handler, without first executing the function explicitly, which doesn't seem like it should be necessary.
My code:
from enum import Enum, auto, unique
import typing
@unique
class CommandType(Enum):
DONE = auto()
PASS = auto()
class MessageProcessor:
def __init__(self):
self.handlers = {}
def register_handler(command_type: CommandType, *args) -> typing.Callable:
def decorator(function: typing.Callable):
def wrapper(self, args):
self.handlers[command_type] = function
return function
return wrapper
return decorator
@register_handler(CommandType.DONE)
def handle_done(self, args):
print("Handler for DONE command. args: ", args)
@register_handler(CommandType.PASS)
def handle_pass(self, args):
print("Handler for PASS command. args: ", args)
def process(self, message: str):
tokens = message.split()
command_type = CommandType[tokens[0]]
args = tokens[1:]
self.handlers[command_type](self, args)
Now if I try executing the following code, I get an error:
m = MessageProcessor()
m.process('DONE arg1 arg2')
Traceback (most recent call last):
File "/Users/......./main.py", line 87, in <module>
m.process('DONE arg1 arg2')
File "/Users/......./main.py", line 81, in process
self.handlers[command_type](self, args)
KeyError: <CommandType.DONE: 1>
However, if I explicitly invoke the methods (with any arguments) first, then I don't get the error:
m = MessageProcessor()
m.handle_done(None)
m.handle_pass(None)
m.process('DONE arg1 arg2')
m.process('PASS arg1 arg2')
Handler for DONE command. args: ['arg1', 'arg2']
Handler for PASS command. args: ['arg1', 'arg2']
Is there a way to properly register these handlers without first having to perform an explicit dummy invocation (i.e. m.handle_done(None))?
I don't want to do something like manually invoke each method in the constructor for the MessageProcessor, I am looking for an approach in which the existence of the decorator is sufficient.
Would I be able to get around this by making handlers static and not bound to an object instance? I was trying that earlier but got tangled up while trying to make it work:
class MessageProcessor:
cls_handlers = {}
def register_handler(command_type: CommandType, *args) -> typing.Callable:
def decorator(function: typing.Callable):
MessageProcessor.cls_handlers[command_type] = function
return function
return decorator
from How to properly register a handler within a class using a decorator?
No comments:
Post a Comment