-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor message handling again #671
base: develop
Are you sure you want to change the base?
Conversation
This syntax is a lot nicer and even makes adding keyword arguments really easy.
04a3ecd
to
5cd55e7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phew, that was long 😅 But very cool.
When introducing such linked concepts like SearchTree
I always get afraid someone will at some point create loops, but you really have to abuse it to do that here, so i think it's safe 😝
if "router" not in namespace: | ||
namespace["router"] = Router("command") | ||
|
||
router = namespace["router"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't these just dicts and the following works?
if "router" not in namespace: | |
namespace["router"] = Router("command") | |
router = namespace["router"] | |
router = namespace.get("router", Router("command")) |
handler_name = self.router.dispatch(message) | ||
except RouteError: | ||
# We use type(self) because we want to properly follow the MRO | ||
handler_name = super(type(self), self).router.dispatch(message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand this correctly as:
- check your own router for a handler
- if not found, check your parent's router for a handler
- if not found, leave the
RouteError
raised by your parent's router unhandled.
In particular, if there is no 4. propagating up to your grandparent's router, why call super
rather than ConnectionMeta
directly?
I also don't understand what "MRO" means, so maybe that's the issue here :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh maybe you want to inherit from Connection
and then check ChildConnection
and Connection
's router only? Still seems slightly weird to only check on parent rather than recursively call super(type(self), self).dispatch(message)
plus base case handling.
:raises: RouteError if no handler was found | ||
""" | ||
handler_func = self.dispatch(message) | ||
return await handler_func(message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So all handlers need to be async?(!)
""" | ||
Send multiple messages in the form of a list of dictionaries. | ||
|
||
May be more optimal than sending a single message. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"more optimal" 😝
with contextlib.suppress(KeyError): | ||
return self[message] | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You seem to favor these instead of try/except now?
|
||
from server.core import Service | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also test that the snake case conversion works if it's not tested yet, but that feels very minor.
foo.reset_mock() | ||
bar.reset_mock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or split it into two tests
with pytest.raises(RouteError): | ||
router.dispatch({"command": "qux"}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also test router.dispatch({"not_command": "foo"})
?
b13ea36
to
c621626
Compare
6df79ac
to
bee3f7f
Compare
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:
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: