-
-
Notifications
You must be signed in to change notification settings - Fork 392
Bots: Add metadata scheme for bots. #66
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
base: main
Are you sure you want to change the base?
Conversation
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.
This will be great! Left a couple stylistic comments. Oh, and there are some linter errors ;)
@@ -18,6 +18,11 @@ class WikipediaHandler(object): | |||
kind of external issue tracker as well. | |||
''' | |||
|
|||
META = {'name': 'Wikipedia', | |||
'description': 'Searches Wikipedia for a term and returns the top article.', | |||
'no_defaults': True, # Let bot handle all messages |
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.
'defaults' might suit here as a name, to avoid negation in parameter names.
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.
Amended, pending push.
@@ -6,6 +6,8 @@ | |||
XKCD_TEMPLATE_URL = 'https://xkcd.com/%s/info.0.json' | |||
LATEST_XKCD_URL = 'https://xkcd.com/info.0.json' | |||
|
|||
from collections import OrderedDict |
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.
Nit: should this go to the other import statements?
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.
Amended, pending push.
zulip_bots/zulip_bots/lib.py
Outdated
('usage', ((lambda: message_handler.usage(), "Bot-provided usage text"))), | ||
('help', (lambda: "{}\n{}\n{}".format(def_about(), message_handler.usage(), def_help()), | ||
"This help text")), | ||
('commands', (def_commands, "A short list of supported commands")) |
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.
Hrmm, this construction is fun, but looks a little brittle.
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.
The outer construction is the form of the OrderedDict constructor, the value is simply lambda+Text (so tuple seems ok?); are you referring to the formatting, the data-structure, or some other aspect?
zulip_bots/zulip_bots/lib.py
Outdated
for k, v in bot_details['commands'].items() if k != '')) | ||
def def_commands(): | ||
return "**Commands**: {} {}".format( | ||
" ".join(k for k in default_commands if k != ''), |
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.
if k
suffices.
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.
Same for the other occurrences.
zulip_bots/zulip_bots/lib.py
Outdated
def def_about(): | ||
desc = bot_details['description'] | ||
return "**{}**{}".format(bot_details['name'], | ||
"" if desc == "" else ": {}".format(desc)) |
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.
return "**{}**{}".format(bot_details['name'], not desc or ": {}".format(desc))
?
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.
Interesting construction; I just played with this but it doesn't seem to work in practice as "not desc" gives True :)
Alternatively, just:
if bot_details['description'] == "":
return "**{}**".format(bot_details['name'])
return "**{}**: {}".format(bot_details['name'], bot_details['description'])
?
zulip_bots/zulip_bots/lib.py
Outdated
]) | ||
# Update bot_details from those in class, if present | ||
try: | ||
bot_details.update(lib_module.handler_class.META) |
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.
bot_details.update(getattr(lib_module.handler_class, 'META'. {})
?
zulip_bots/zulip_bots/lib.py
Outdated
for command in default_commands: | ||
if command == '': | ||
continue | ||
if message['content'].startswith(command): |
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.
Maybe we want a simple equality check here, so we can simplify the whole # Handle any default_commands first
if clause?
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.
To clarify, do you mean something like if default_commands == {}:
, or even if default_commands:
?
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.
Yeah, I think we want an equality check, not startswith. It also means ''
doesn't need to be a special case.
zulip_bots/zulip_bots/lib.py
Outdated
return "**Commands**: {} {}".format( | ||
" ".join(k for k in default_commands if k != ''), | ||
" ".join(k for k in bot_details['commands'] if k != '')) | ||
default_commands = OrderedDict([ |
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.
Why does this need to be an ordered dict?
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.
If these are not ordered, then when iterated in the commands and help text, the order is not guaranteed - perhaps not vital for regular use, but tests won't be consistent either!
zulip_bots/zulip_bots/lib.py
Outdated
del default_commands[command] | ||
# Sync default_commands changes with bot_details | ||
if len(default_commands) == 0: | ||
bot_details['no_defaults'] = True |
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.
I've not entirely looked through this bot_detail
default_commands
code logic. Maybe this would be easier to understand if we outsource it to a separate function?
fff413c
to
6de4f99
Compare
Updated to reflect most review comments; pending are final if statement and linter (since code has changed). |
6de4f99
to
db9934b
Compare
('latest', "Show the latest comic strip"), | ||
('random', "Show a random comic strip"), | ||
('<comic id>', "Show a comic strip with a specific 'comic id'"), | ||
]) # NOTE: help not listed here, so default command used |
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.
I don't understand this comment.
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.
In this bot, with this PR, help is now handled internally by the lib, so the default command is used instead as it stands. If 'help' was listed in commands
then the bot wouldn't handle it and the help text would be as specified in the tuple.
zulip_bots/zulip_bots/lib.py
Outdated
return defaults | ||
|
||
def sync_botdetails_defaultcommands(bot_details, default_commands): | ||
# Update default_commands from any changes in bot_details |
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.
I don't understand this function; is it just "updating" in the sense that it's overriding the global defaults with this particular bot's settings?
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.
I broke out the code which adjusted default_commands
based on bot_details
(in some cases), but also have the last conditional which set default_commands_enabled
. That's only (currently) used in that function, so could be cut, but that's why I prefixed it with 'sync'.
META = { | ||
'name': 'Wikipedia', | ||
'description': 'Searches Wikipedia for a term and returns the top article.', | ||
'defaults': False, # Let bot handle all messages |
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.
I think the fact that you feel the need for a comment here suggests we might want a more verbose name than "defaults". I think "default_commands_enabled" would be a good name.
zulip_bots/zulip_bots/lib.py
Outdated
def def_about(): | ||
if bot_details['description'] == "": | ||
return "**{}**".format(bot_details['name']) | ||
return "**{}**: {}".format(bot_details['name'], bot_details['description']) |
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.
This doesn't feel particularly readable. A better approach might be to write these as as e.g.
"**{name}**: {description}".format(**bot_details)
and probably even better would be e.g.
`"%(name)s: %(description)s" % bot_details
zulip_bots/zulip_bots/lib.py
Outdated
return ("\n".join("**{}** - {}".format(k, v[1]) | ||
for k, v in defaults.items() if k) + "\n" + | ||
"\n".join("**{}** - {}".format(k, v) | ||
for k, v in bot_details['commands'].items() if k)) |
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.
Let's use more verbose variable names than k/v here. I think we want "command_name" and "command_description".
I think some of the other new functions in this file could use the same sort of work.
As a general rule, one should basically always be verbose in variable names. Brevity doesn't provide much value, and the extra clarity is huge.
Also, put the newline on its own line so it is more visible.
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.
- I've generally been wary of verbosity here (not my code), perhaps because I'm aware how verbose I've been in my own code! (and I've generally gone very verbose, and wanted to fit in shortish lines here).
- I've gone for variables without the
command_
prefix, which I think provides a balanced approach. - The newline was on it's own line in a previous version; moved it back :)
zulip_bots/zulip_bots/lib.py
Outdated
('about', (def_about, "The type and use of this bot")), | ||
('usage', ((lambda: message_handler.usage(), "Bot-provided usage text"))), | ||
('help', (lambda: "{}\n{}\n{}".format(def_about(), message_handler.usage(), def_help()), | ||
"This help text")), |
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.
I think the lambdas here aren't super readable; I'd rather those have an actual def
method like the others.
zulip_bots/zulip_bots/lib.py
Outdated
del default_commands[command] | ||
# Sync default_commands changes with bot_details | ||
if len(default_commands) == 0: | ||
bot_details['defaults'] = False |
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.
This function would be much better as something that returned a new dictionary based on the inputs, rather than mutating the default_commands dict. In addition to being a cleaner flow, I'm pretty sure the mutation means this code ends up being broken if multiple bots with different command sets run in the same process.
ccb9841
to
b775c48
Compare
@timabbott Changes made in a new commit to show the changes separately, though will squash or split as necessary. |
Hello @neiljp, it seems like you have referenced an issue in your pull request, but you have not referenced that issue in your commit message(s). When you reference an issue in a commit message, it automatically closes the corresponding issue when the commit is merged. Please run An example of a correctly-formatted commit:
Thank you for your contributions to Zulip! |
Current status is: (copying from zulip, to keep the context)
|
@neiljp I find this rather hard to review - it'd be awesome if you could split this in multiple, easily digestible commits; e.g. one for updating a bot with the feature, one for adding default commands, one for the core update command logic, etc. Additionally, I think this would really profit from some unittests, which could for example be added in the updated bots themselves. |
b775c48
to
9149ca9
Compare
Hello @neiljp, it seems like you have referenced an issue in your pull request, but you have not referenced that issue in your commit message(s). When you reference an issue in a commit message, it automatically closes the corresponding issue when the commit is merged. Please run An example of a correctly-formatted commit:
Thank you for your contributions to Zulip! |
7f2d224
to
7926511
Compare
Hello @neiljp, it seems like you have referenced an issue in your pull request, but you have not referenced that issue in your commit message(s). When you reference an issue in a commit message, it automatically closes the corresponding issue when the commit is merged. Please run An example of a correctly-formatted commit:
Thank you for your contributions to Zulip! |
7926511
to
d0cc55c
Compare
Hello @neiljp, it seems like you have referenced an issue in your pull request, but you have not referenced that issue in your commit message(s). When you reference an issue in a commit message, it automatically closes the corresponding issue when the commit is merged. Please run An example of a correctly-formatted commit:
Thank you for your contributions to Zulip! |
I've refactored some of these commits to be smaller and clearer, and added some which amend the testing framework and I still would like to think/discuss about the way commands might work, but this is getting closer, I feel. |
d0cc55c
to
4e9f101
Compare
Currently the CI is failing, since I'm unsure whether to encode the complex default command responses in the tests, when they're so automated from the other data? That is, should I update the "help" text for the xkcd bot to ensure it passes, or remove that test? |
@@ -168,6 +168,14 @@ def is_private_message_from_another_user(message_dict, current_user_id): | |||
return current_user_id != message_dict['sender_id'] | |||
return False | |||
|
|||
def get_bot_details(bot_class, bot_name): |
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.
LGTm for the commit ffc977a
@@ -168,10 +170,30 @@ def is_private_message_from_another_user(message_dict, current_user_id): | |||
return current_user_id != message_dict['sender_id'] | |||
return False | |||
|
|||
def setup_default_commands(bot_details, message_handler): |
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.
I suppose this is the nucleus of the PR.
Hello @neiljp, a Zulip maintainer reviewed this pull request over 7 days ago, but you haven't updated your pull request since. Please take a look at the requested changes and update your pull request accordingly. Thank you for your valuable contributions to Zulip! |
Hi @neiljp Can you resolve the conflicts here? |
- empty: solves common case where "" message not handled in bots - about: uses new name/description META information from bots - usage: re-uses existing bot usage() function (remove/adjust later?)
4e9f101
to
34b5f3a
Compare
Heads up @neiljp, we just merged some commits that conflict with the changes your made in this pull request! You can review this repository's recent commits to see where the conflicts occur. Please rebase your feature branch against the |
The concepts here are pretty valuable, but the PR has become too stale. I think we can close it but it's definitely worth doing a follow-up on this. |
@PIG208 This PR is very old now and out of date, but my recollection was of various driving elements:
|
Note that #169 takes forward one of these points forward. |
(about, '' (empty), usage [remove?], help/commands)
This is an alternative/supplement/extension of #58, which would also resolv #55.
I also raised the idea of bot metadata in #integrations.
Currently this just provides default-commands which use the simple metadata and (optionally) a list of bot commands provided by the developer. It could allow the user to provide callbacks directly, with the handle_message in the bot only being used for more complex input (like zulip/zulip#5047)