hawkins / Shawk

Library to Send & Receive SMS for Free using Email or Your Phone Number
https://shawk.now.sh/
MIT License
16 stars 4 forks source link

Add decorators for handler functions that are called if Message fits a regex #8

Closed hawkins closed 7 years ago

hawkins commented 8 years ago

It makes sense that alternate behaviors could be defined. These could be decided by a regular expression to be tested against the message body or sender in some manner.

User-facing

For instance, maybe my application defines controls, and I send a lot of text messages. A 'silence' command would be useful. Let's match any message body starting with 'silence' here:

@client.handle('/^silence/')
def handle_silence(client, msg):
    # ... some computation here ...

Note the use of @client instead of just @shawk. This is to allow the use of multiple clients, where each has different handler functions attached. I'm only a novice with decorators, so the exact syntax here may vary depending on actual capabilities of decorators.

Maybe we could instead match the sender's name to only handle requests from 'Josh' with this:

@client.handle(name='/^Josh$/')
def handle_Josh(client, msg):
    # ... some computation here ...

Note the regex is overkill in this example (and the first, to an extent, but its only a proof of concept), so if the provided string is not explicitly a regex with the /.*/ pattern, then we'll treat it as a string for equality comparison.

Of course, if we receive a message that doesn't fit any of our defined handler patterns, we'll need a fallback default case. This could look like this:

@client.handle()
def handle_default(client, msg):
    # ... some computation here ...

Implementation

Since @decorator(args)\ndef some_function(): is just syntactic sugar for decorator(some_function, args) or thereabouts, Client.handler may be changed to Client.handlers = {} which is a dictionary mapping regular expressions to their associated handler function.

I hate to just iterate over the dictionary, but I suppose we could iterate over the items in the dictionary...

matched = False
for key, val in self.handlers.items():
    if key.match(Message.text):
        # Call the handler function
        val(self, Message)
        matched = True

if not matched:
    # Call the default handler
    self.default_handler(self, Message)

This behavior would allow multiple regex matches matches, yet disable the default once a more specific match was made.

hawkins commented 8 years ago

I'm not sure a handler for author is very useful. It's of course not necessary (handlers could behave a certain way based on text content, then check author to behave appropriately if that matters), but it doesn't quite seem right. For now, I've got @client.text_handler(regex, modifiers) implemented in 24ba28887caeabd351dcc71fda90cbcee88d431d.

Will leave this open in case any other ideas for handling authors pop up.

hawkins commented 7 years ago

In https://github.com/hawkins/Shawk/commit/e4dbd5c5d870a99e13e3f298593ade3707673f53 I added a @client.contact_handler(shawk.Contact). Additionally I revamped text_handler to associate multiple functions with a regex, just to be more flexible, just like contact_handler does.

I also added add_contact/text_handler and remove_contact/text_handler functions. These allow you to avoid decorators, which opens up the doors for more complicated behaviors without the need for unnecessary deep indentation nesting. (I'll open an issue about documenting examples of advanced usage for this due at 1.0)

The trouble with the remove_text_handler function is that you need the compiled regex to remove the handler function. So if you use the @client.text_handler() decorator, you have to receive the regex back as an argument to the decorated function in order to be able to remove the function in the future. Thus the new signature for text_handlers is: def func (client, message, match, regex):. It feels ridiculous to even send the match if the regex is provided too, but I suppose its unnecessary to calculate the match twice, too, given it could be a very complicated regex. So, decorators might not be the ideal solution here in the long run - will need to keep an eye out for this.

I'm closing this issue now as its original purpose has been completed, but the changes introduced to support this issue may be reworked at a later date.