prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  announce error on python3
  <!-- Before reporting a bug, please search both open *and closed* issues to
  see if it has already been reported. If you can, try to reproduce the problem
  on an unmodified copy of the `master` branch first, as sometimes bugs are found
  and fixed without a report. If the problem is unreported and persists in
  `master`, please help us fix it quickly by filling out as much of this
  information as you can. Thanks! -->
  
  ### Description
  .announce results in an error on python 3
  
  ### Reproduction steps
  1. Setup a instance on python3 (specifically I got the error on v3.7)
  2. Try to use .announce
  3. there will be a error
  
  ### Expected behavior
  Works without errors.
  
  ### Logs
  ```
  If applicable, add logs to help us figure out what's happening. Raw logs are
  super helpful! Logs are usually found in ~/.sopel/logs, depending on your
  configuration.
  ```
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: [2021-07-22 17:04:51,684] sopel.bot            ERROR    - Unexpected error ('dict_keys' object is not subscriptable) from MacFan4000 at 2
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: Traceback (most recent call last):
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/bot.py", line 757, in call_rule
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     rule.execute(sopel, trigger)
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/plugins/rules.py", line 1057, in execute
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     exit_code = self._handler(bot, trigger)
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/plugin.py", line 1071, in guarded
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     return function(bot, trigger, *args, **kwargs)
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/modules/announce.py", line 44, in announce
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     for cgroup in channels:
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File "/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/modules/announce.py", line 24, in _chunks
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     yield items[delim:delim + size]
  Jul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: TypeError: 'dict_keys' object is not subscriptable
  ### Environment
  - Sopel `.version`: [e.g. 7.0.0 or d416e19] 7.1.2
  - Sopel installed via: [apt, pip, `setup.py install`, source, ?] pip
  - Python version: [e.g. 3.6.9] 3.7
  - Operating system: [e.g. Debian 10] Debian Buster
  - IRCd `/version`: [e.g. InspIRCd 3.0.1] Libera Chat
  - Relevant plugins: [adminchannel, weather, custom\_thing.py, ?] announce
  
  ### Notes
  I believe https://github.com/sopel-irc/sopel/commit/b7b6b46a84e29e26a6a6b921debf57735661a4c0#diff-a9aa50736c17c299dac1ad9cb5ea1b835fb638c91bbd8c547990ffd9d67daa74 broke it due to .keys() not working the same way on python3 as it does on python2.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `sopel/modules/announce.py`
  Content:
  ```
  1 """
  2 announce.py - Sopel Announcement Plugin
  3 Sends announcements to all channels the bot has joined.
  4 Copyright © 2013, Elad Alfassa, <elad@fedoraproject.org>
  5 Licensed under the Eiffel Forum License 2.
  6 
  7 https://sopel.chat
  8 """
  9 from __future__ import generator_stop
  10 
  11 from sopel import plugin
  12 
  13 
  14 def _chunks(items, size):
  15     """Break a list of items into groups.
  16 
  17     :param items: the collection of items to chunk
  18     :type items: :term:`iterable`
  19     :param int size: the size of each chunk
  20     """
  21     # from https://stackoverflow.com/a/312464/5991 with modified names for readability
  22     for delim in range(0, len(items), size):
  23         yield items[delim:delim + size]
  24 
  25 
  26 @plugin.command('announce')
  27 @plugin.example('.announce Some important message here')
  28 @plugin.require_admin('Sorry, I can\'t let you do that', reply=True)
  29 @plugin.output_prefix('[ANNOUNCEMENT] ')
  30 def announce(bot, trigger):
  31     """Send an announcement to all channels the bot is in."""
  32     if trigger.group(2) is None:
  33         bot.reply('Announce what? I need a message to say.')
  34         return
  35 
  36     size = 1
  37     try:
  38         size = bot.isupport.TARGMAX.get('PRIVMSG', size)
  39     except AttributeError:
  40         pass
  41 
  42     channels = _chunks(bot.channels.keys(), size)
  43     for cgroup in channels:
  44         bot.say(trigger.group(2), ','.join(cgroup))
  45 
  46     bot.reply('Announce complete.')
  47
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/sopel/modules/announce.py b/sopel/modules/announce.py
  --- a/sopel/modules/announce.py
  +++ b/sopel/modules/announce.py
  @@ -8,6 +8,8 @@
   """
   from __future__ import generator_stop
   
  +import itertools
  +
   from sopel import plugin
   
   
  @@ -17,10 +19,18 @@
       :param items: the collection of items to chunk
       :type items: :term:`iterable`
       :param int size: the size of each chunk
  +    :return: a :term:`generator` of chunks
  +    :rtype: :term:`generator` of :class:`tuple`
       """
  -    # from https://stackoverflow.com/a/312464/5991 with modified names for readability
  -    for delim in range(0, len(items), size):
  -        yield items[delim:delim + size]
  +    # This approach is safer than slicing with non-subscriptable types,
  +    # for example `dict_keys` objects
  +    iterator = iter(items)
  +    # TODO: Simplify to assignment expression (`while cond := expr`)
  +    # when dropping Python 3.7
  +    chunk = tuple(itertools.islice(iterator, size))
  +    while chunk:
  +        yield chunk
  +        chunk = tuple(itertools.islice(iterator, size))
   
   
   @plugin.command('announce')
  
verification_info: {"golden_diff": "diff --git a/sopel/modules/announce.py b/sopel/modules/announce.py\n--- a/sopel/modules/announce.py\n+++ b/sopel/modules/announce.py\n@@ -8,6 +8,8 @@\n \"\"\"\n from __future__ import generator_stop\n \n+import itertools\n+\n from sopel import plugin\n \n \n@@ -17,10 +19,18 @@\n     :param items: the collection of items to chunk\n     :type items: :term:`iterable`\n     :param int size: the size of each chunk\n+    :return: a :term:`generator` of chunks\n+    :rtype: :term:`generator` of :class:`tuple`\n     \"\"\"\n-    # from https://stackoverflow.com/a/312464/5991 with modified names for readability\n-    for delim in range(0, len(items), size):\n-        yield items[delim:delim + size]\n+    # This approach is safer than slicing with non-subscriptable types,\n+    # for example `dict_keys` objects\n+    iterator = iter(items)\n+    # TODO: Simplify to assignment expression (`while cond := expr`)\n+    # when dropping Python 3.7\n+    chunk = tuple(itertools.islice(iterator, size))\n+    while chunk:\n+        yield chunk\n+        chunk = tuple(itertools.islice(iterator, size))\n \n \n @plugin.command('announce')\n", "issue": "announce error on python3\n<!-- Before reporting a bug, please search both open *and closed* issues to\r\nsee if it has already been reported. If you can, try to reproduce the problem\r\non an unmodified copy of the `master` branch first, as sometimes bugs are found\r\nand fixed without a report. If the problem is unreported and persists in\r\n`master`, please help us fix it quickly by filling out as much of this\r\ninformation as you can. Thanks! -->\r\n\r\n### Description\r\n.announce results in an error on python 3\r\n\r\n### Reproduction steps\r\n1. Setup a instance on python3 (specifically I got the error on v3.7)\r\n2. Try to use .announce\r\n3. there will be a error\r\n\r\n### Expected behavior\r\nWorks without errors.\r\n\r\n### Logs\r\n```\r\nIf applicable, add logs to help us figure out what's happening. Raw logs are\r\nsuper helpful! Logs are usually found in ~/.sopel/logs, depending on your\r\nconfiguration.\r\n```\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: [2021-07-22 17:04:51,684] sopel.bot            ERROR    - Unexpected error ('dict_keys' object is not subscriptable) from MacFan4000 at 2\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: Traceback (most recent call last):\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/bot.py\", line 757, in call_rule\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     rule.execute(sopel, trigger)\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/plugins/rules.py\", line 1057, in execute\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     exit_code = self._handler(bot, trigger)\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/plugin.py\", line 1071, in guarded\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     return function(bot, trigger, *args, **kwargs)\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/modules/announce.py\", line 44, in announce\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     for cgroup in channels:\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:   File \"/srv/sopelbots/prodvenv/lib/python3.7/site-packages/sopel/modules/announce.py\", line 24, in _chunks\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]:     yield items[delim:delim + size]\r\nJul 22 17:04:51 bots1.mirahezebots.org sopel[31484]: TypeError: 'dict_keys' object is not subscriptable\r\n### Environment\r\n- Sopel `.version`: [e.g. 7.0.0 or d416e19] 7.1.2\r\n- Sopel installed via: [apt, pip, `setup.py install`, source, ?] pip\r\n- Python version: [e.g. 3.6.9] 3.7\r\n- Operating system: [e.g. Debian 10] Debian Buster\r\n- IRCd `/version`: [e.g. InspIRCd 3.0.1] Libera Chat\r\n- Relevant plugins: [adminchannel, weather, custom\\_thing.py, ?] announce\r\n\r\n### Notes\r\nI believe https://github.com/sopel-irc/sopel/commit/b7b6b46a84e29e26a6a6b921debf57735661a4c0#diff-a9aa50736c17c299dac1ad9cb5ea1b835fb638c91bbd8c547990ffd9d67daa74 broke it due to .keys() not working the same way on python3 as it does on python2.\r\n\n", "before_files": [{"content": "\"\"\"\nannounce.py - Sopel Announcement Plugin\nSends announcements to all channels the bot has joined.\nCopyright \u00a9 2013, Elad Alfassa, <elad@fedoraproject.org>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import generator_stop\n\nfrom sopel import plugin\n\n\ndef _chunks(items, size):\n    \"\"\"Break a list of items into groups.\n\n    :param items: the collection of items to chunk\n    :type items: :term:`iterable`\n    :param int size: the size of each chunk\n    \"\"\"\n    # from https://stackoverflow.com/a/312464/5991 with modified names for readability\n    for delim in range(0, len(items), size):\n        yield items[delim:delim + size]\n\n\n@plugin.command('announce')\n@plugin.example('.announce Some important message here')\n@plugin.require_admin('Sorry, I can\\'t let you do that', reply=True)\n@plugin.output_prefix('[ANNOUNCEMENT] ')\ndef announce(bot, trigger):\n    \"\"\"Send an announcement to all channels the bot is in.\"\"\"\n    if trigger.group(2) is None:\n        bot.reply('Announce what? I need a message to say.')\n        return\n\n    size = 1\n    try:\n        size = bot.isupport.TARGMAX.get('PRIVMSG', size)\n    except AttributeError:\n        pass\n\n    channels = _chunks(bot.channels.keys(), size)\n    for cgroup in channels:\n        bot.say(trigger.group(2), ','.join(cgroup))\n\n    bot.reply('Announce complete.')\n", "path": "sopel/modules/announce.py"}], "after_files": [{"content": "\"\"\"\nannounce.py - Sopel Announcement Plugin\nSends announcements to all channels the bot has joined.\nCopyright \u00a9 2013, Elad Alfassa, <elad@fedoraproject.org>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat\n\"\"\"\nfrom __future__ import generator_stop\n\nimport itertools\n\nfrom sopel import plugin\n\n\ndef _chunks(items, size):\n    \"\"\"Break a list of items into groups.\n\n    :param items: the collection of items to chunk\n    :type items: :term:`iterable`\n    :param int size: the size of each chunk\n    :return: a :term:`generator` of chunks\n    :rtype: :term:`generator` of :class:`tuple`\n    \"\"\"\n    # This approach is safer than slicing with non-subscriptable types,\n    # for example `dict_keys` objects\n    iterator = iter(items)\n    # TODO: Simplify to assignment expression (`while cond := expr`)\n    # when dropping Python 3.7\n    chunk = tuple(itertools.islice(iterator, size))\n    while chunk:\n        yield chunk\n        chunk = tuple(itertools.islice(iterator, size))\n\n\n@plugin.command('announce')\n@plugin.example('.announce Some important message here')\n@plugin.require_admin('Sorry, I can\\'t let you do that', reply=True)\n@plugin.output_prefix('[ANNOUNCEMENT] ')\ndef announce(bot, trigger):\n    \"\"\"Send an announcement to all channels the bot is in.\"\"\"\n    if trigger.group(2) is None:\n        bot.reply('Announce what? I need a message to say.')\n        return\n\n    size = 1\n    try:\n        size = bot.isupport.TARGMAX.get('PRIVMSG', size)\n    except AttributeError:\n        pass\n\n    channels = _chunks(bot.channels.keys(), size)\n    for cgroup in channels:\n        bot.say(trigger.group(2), ','.join(cgroup))\n\n    bot.reply('Announce complete.')\n", "path": "sopel/modules/announce.py"}]}