Python decorators and testing interplay
- Semprini
- July 17, 2020
- 0
I've been watching a Twitch streamer called beginbot. In his entertaining if somewhat manic style that talented programmers often have, he encourages his viewers to get involved by creating chatbots.
I decided to have a go and created a bot which implements a basic AI and uses natural language processing. The result of this can be found here: https://github.com/Semprini/blortbot
Initially I had a dictionary of command names and command functions but I decided a better approach would be to have a decorator which adds functions to the dictionary automatically:
def command(name, desc):
....@functools.wraps(name, desc)
....def wrapper(func):
........"""Register a function as a command"""
........COMMANDS[COMMAND_TRIGGER + name] = (func, desc)
........return func
....return wrapper
Cool, so I could simply annotate my command functions like so:
@command('hello', 'responds with hello')
def command_hello(bot, user, msg):
....bot.send_message(f"Hello {user}")
And I placed my functions in the __init__.py of my commands folder to allow for future expansion.
Awesome, and since I fool myself that I'm a decent programmer, I have a token effort at some unit tests and all is well. I bask in the majesty of my test success. I'm then off to unleash my bot on the unsuspecting beginbot community and ... nothing. Exactly the same input was not producing the same result???
Eventually I figured out that the unit test framework was going looking for tests to run and imported my commands module which registered the commands behind my back thus giving my false positive.
(see update below) The simple fix was to import the commands module. This caused another little issue - I was simply importing the module but not directly using the module so when my linter (flake8) ran it told me I was naughty for having unused imports. The only fix I have for this is to ignore the warning:
import commands as command_set # noqa: F401
My bot is now in the wild and free to pursue a life of religious fulfilment. Stop by beginland on twitch and ask it anything.
> !learn kung fu
blortbot: I know kung fu
> @blortbot who was bruce lee's teacher
blortbot: yip man was the teacher of [[bruce lee]].
*update: I realised that I was abusing the purpose of imports (thanks to anthonywritescode) so flake8 was right to not be happy. I refactored the import code to directly go looking for commands in a given path:
command_module = __import__(command_module_name)
for _, module_name, _ in pkgutil.walk_packages(...):
....__import__(module_name, fromlist=['__trash'], level=0)