server
server copied to clipboard
Refactor message handling again
This is something I've been pondering for a while, and have recently learned enough about python internals to figure out how to do it!
Instead of mapping messages to handlers by using string formatting on attribute names, or by explicitly listing every handler in a giant dict, the mapping is generated automatically at import time using a metaclass. It also doesn't use a single key mapping, but a search tree which is able to match an arbitrary number of subcommands.
To create a connection:
from server.core.connection import Connection, handler
class MyConn(Connection):
@handler("foo")
async def handle_foo(self, message):
print("foo")
@handler("foo", bar="baz")
async def handle_foo_bar(self, message):
print("foobar")
@handler(bar="baz")
async def handle_bar_baz(self, message):
print("barbaz")
conn = MyConn(proto, addr)
await conn.on_message_received({"command": "foo"}) # foo
await conn.on_message_received({"command": "foo", "bar": "qux"}) # foo
await conn.on_message_received({"command": "foo", "bar": "baz"}) # foobar
await conn.on_message_received({"bar": "baz"}) # barbaz
One thing I still want to do in the future is figure out a nice way to modularize the command handlers so they can be split across multiple files. For instance we could put all of the "admin" commands in one file and all of the "social" commands in another. Right now it could be done with subclasses, but that doesn't seem like the most elegant solution to me.
I also refactored the service registration so it no longer needs a metaclass, and supports overriding the implicitly derived service name like this:
from server.core import Service
class MyService(Service, name="my_thing"):
pass
class FooService(Service):
def __init__(self, my_thing):
assert isinstance(my_thing, MyService)