diff options
| author | 2019-09-10 18:49:30 -0400 | |
|---|---|---|
| committer | 2019-09-10 18:49:30 -0400 | |
| commit | 77c9b0f2194072b19616bad4ce81f72ebcdad27d (patch) | |
| tree | 1c0efc3f8315617ef7dfdb24d62b5df21e3b29d6 | |
| parent | Docstring linting chunk 3 (diff) | |
Docstring linting chunk 4
| -rw-r--r-- | bot/cogs/bot.py | 71 | ||||
| -rw-r--r-- | bot/cogs/doc.py | 133 | ||||
| -rw-r--r-- | bot/cogs/error_handler.py | 6 | ||||
| -rw-r--r-- | bot/cogs/eval.py | 22 | ||||
| -rw-r--r-- | bot/cogs/jams.py | 13 | ||||
| -rw-r--r-- | bot/cogs/site.py | 28 | ||||
| -rw-r--r-- | bot/cogs/utils.py | 28 | 
7 files changed, 125 insertions, 176 deletions
| diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index 828e2514c..be7922ef9 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -2,6 +2,7 @@ import ast  import logging  import re  import time +from typing import Optional, Tuple, Union  from discord import Embed, Message, RawMessageUpdateEvent  from discord.ext.commands import Bot, Context, command, group @@ -17,9 +18,7 @@ log = logging.getLogger(__name__)  class Bot: -    """ -    Bot information commands -    """ +    """Bot information commands."""      def __init__(self, bot: Bot):          self.bot = bot @@ -46,20 +45,14 @@ class Bot:      @group(invoke_without_command=True, name="bot", hidden=True)      @with_role(Roles.verified) -    async def bot_group(self, ctx: Context): -        """ -        Bot informational commands -        """ - +    async def bot_group(self, ctx: Context) -> None: +        """Bot informational commands."""          await ctx.invoke(self.bot.get_command("help"), "bot")      @bot_group.command(name='about', aliases=('info',), hidden=True)      @with_role(Roles.verified) -    async def about_command(self, ctx: Context): -        """ -        Get information about the bot -        """ - +    async def about_command(self, ctx: Context) -> None: +        """Get information about the bot."""          embed = Embed(              description="A utility bot designed just for the Python server! Try `!help` for more info.",              url="https://gitlab.com/discord-python/projects/bot" @@ -77,24 +70,18 @@ class Bot:      @command(name='echo', aliases=('print',))      @with_role(*MODERATION_ROLES) -    async def echo_command(self, ctx: Context, *, text: str): -        """ -        Send the input verbatim to the current channel -        """ - +    async def echo_command(self, ctx: Context, *, text: str) -> None: +        """Send the input verbatim to the current channel."""          await ctx.send(text)      @command(name='embed')      @with_role(*MODERATION_ROLES) -    async def embed_command(self, ctx: Context, *, text: str): -        """ -        Send the input within an embed to the current channel -        """ - +    async def embed_command(self, ctx: Context, *, text: str) -> None: +        """Send the input within an embed to the current channel."""          embed = Embed(description=text)          await ctx.send(embed=embed) -    def codeblock_stripping(self, msg: str, bad_ticks: bool): +    def codeblock_stripping(self, msg: str, bad_ticks: bool) -> Union[Tuple[Tuple[str, Optional[str]], str], None]:          """          Strip msg in order to find Python code. @@ -163,15 +150,10 @@ class Bot:                      log.trace(f"Returning message.\n\n{content}\n\n")                      return (content,), repl_code -    def fix_indentation(self, msg: str): -        """ -        Attempts to fix badly indented code. -        """ - -        def unindent(code, skip_spaces=0): -            """ -            Unindents all code down to the number of spaces given ins skip_spaces -            """ +    def fix_indentation(self, msg: str) -> str: +        """Attempts to fix badly indented code.""" +        def unindent(code, skip_spaces: int = 0) -> str: +            """Unindents all code down to the number of spaces given in skip_spaces."""              final = ""              current = code[0]              leading_spaces = 0 @@ -207,11 +189,13 @@ class Bot:              msg = f"{first_line}\n{unindent(code, 4)}"          return msg -    def repl_stripping(self, msg: str): +    def repl_stripping(self, msg: str) -> Tuple[str, bool]:          """          Strip msg in order to extract Python code out of REPL output.          Tries to strip out REPL Python code out of msg and returns the stripped msg. + +        Returns a second boolean output if REPL code was found in the input msg.          """          final = ""          for line in msg.splitlines(keepends=True): @@ -225,7 +209,8 @@ class Bot:              log.trace(f"Found REPL code in \n\n{msg}\n\n")              return final.rstrip(), True -    def has_bad_ticks(self, msg: Message): +    def has_bad_ticks(self, msg: Message) -> bool: +        """Check to see if msg contains ticks that aren't '`'."""          not_backticks = [              "'''", '"""', "\u00b4\u00b4\u00b4", "\u2018\u2018\u2018", "\u2019\u2019\u2019",              "\u2032\u2032\u2032", "\u201c\u201c\u201c", "\u201d\u201d\u201d", "\u2033\u2033\u2033", @@ -234,13 +219,13 @@ class Bot:          return msg.content[:3] in not_backticks -    async def on_message(self, msg: Message): -        """ -        Detect poorly formatted Python code and send the user -        a helpful message explaining how to do properly -        formatted Python syntax highlighting codeblocks. +    async def on_message(self, msg: Message) -> None:          """ +        Detect poorly formatted Python code in new messages. +        If poorly formatted code is detected, send the user a helpful message explaining how to do +        properly formatted Python syntax highlighting codeblocks. +        """          parse_codeblock = (              (                  msg.channel.id in self.channel_cooldowns @@ -355,7 +340,8 @@ class Bot:                          f"The message that was posted was:\n\n{msg.content}\n\n"                      ) -    async def on_raw_message_edit(self, payload: RawMessageUpdateEvent): +    async def on_raw_message_edit(self, payload: RawMessageUpdateEvent) -> None: +        """Check to see if an edited message (previously called out) still contains poorly formatted code."""          if (              # Checks to see if the message was called out by the bot              payload.message_id not in self.codeblock_message_ids @@ -381,6 +367,7 @@ class Bot:              log.trace("User's incorrect code block has been fixed.  Removing bot formatting message.") -def setup(bot): +def setup(bot: Bot) -> None: +    """Bot cog load."""      bot.add_cog(Bot(bot))      log.info("Cog loaded: Bot") diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index aa49b0c25..ef14d1797 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -4,10 +4,11 @@ import logging  import re  import textwrap  from collections import OrderedDict -from typing import Optional, Tuple +from typing import Callable, Optional, Tuple  import discord  from bs4 import BeautifulSoup +from bs4.element import PageElement  from discord.ext import commands  from markdownify import MarkdownConverter  from requests import ConnectionError @@ -27,24 +28,22 @@ UNWANTED_SIGNATURE_SYMBOLS = ('[source]', '¶')  WHITESPACE_AFTER_NEWLINES_RE = re.compile(r"(?<=\n\n)(\s+)") -def async_cache(max_size=128, arg_offset=0): +def async_cache(max_size: int = 128, arg_offset: int = 0) -> Callable:      """      LRU cache implementation for coroutines. -    :param max_size: -    Specifies the maximum size the cache should have. -    Once it exceeds the maximum size, keys are deleted in FIFO order. -    :param arg_offset: -    The offset that should be applied to the coroutine's arguments -    when creating the cache key. Defaults to `0`. -    """ +    Once the cache exceeds the maximum size, keys are deleted in FIFO order. +    An offset may be optionally provided to be applied to the coroutine's arguments when creating the cache key. +    """      # Assign the cache to the function itself so we can clear it from outside.      async_cache.cache = OrderedDict() -    def decorator(function): +    def decorator(function: Callable) -> Callable: +        """Define the async_cache decorator."""          @functools.wraps(function) -        async def wrapper(*args): +        async def wrapper(*args) -> OrderedDict: +            """Decorator wrapper for the caching logic."""              key = ':'.join(args[arg_offset:])              value = async_cache.cache.get(key) @@ -59,27 +58,25 @@ def async_cache(max_size=128, arg_offset=0):  class DocMarkdownConverter(MarkdownConverter): -    def convert_code(self, el, text): -        """Undo `markdownify`s underscore escaping.""" +    """Subclass markdownify's MarkdownCoverter to provide custom conversion methods.""" +    def convert_code(self, el: PageElement, text: str) -> str: +        """Undo `markdownify`s underscore escaping."""          return f"`{text}`".replace('\\', '') -    def convert_pre(self, el, text): +    def convert_pre(self, el: PageElement, text: str) -> str:          """Wrap any codeblocks in `py` for syntax highlighting.""" -          code = ''.join(el.strings)          return f"```py\n{code}```" -def markdownify(html): +def markdownify(html: str) -> DocMarkdownConverter: +    """Create a DocMarkdownConverter object from the input html."""      return DocMarkdownConverter(bullets='•').convert(html)  class DummyObject(object): -    """ -    A dummy object which supports assigning anything, -    which the builtin `object()` does not support normally. -    """ +    """A dummy object which supports assigning anything, which the builtin `object()` does not support normally."""  class SphinxConfiguration: @@ -94,14 +91,15 @@ class InventoryURL(commands.Converter):      """      Represents an Intersphinx inventory URL. -    This converter checks whether intersphinx -    accepts the given inventory URL, and raises +    This converter checks whether intersphinx accepts the given inventory URL, and raises      `BadArgument` if that is not the case. +      Otherwise, it simply passes through the given URL.      """      @staticmethod -    async def convert(ctx, url: str): +    async def convert(ctx: commands.Context, url: str) -> str: +        """Convert url to Intersphinx inventory URL."""          try:              intersphinx.fetch_inventory(SphinxConfiguration(), '', url)          except AttributeError: @@ -121,30 +119,32 @@ class InventoryURL(commands.Converter):  class Doc: -    def __init__(self, bot): +    """A set of commands for querying & displaying documentation.""" + +    def __init__(self, bot: commands.Bot):          self.base_urls = {}          self.bot = bot          self.inventories = {} -    async def on_ready(self): +    async def on_ready(self) -> None: +        """Refresh documentation inventory."""          await self.refresh_inventory()      async def update_single(          self, package_name: str, base_url: str, inventory_url: str, config: SphinxConfiguration -    ): +    ) -> None:          """          Rebuild the inventory for a single package. -        :param package_name: The package name to use, appears in the log. -        :param base_url: The root documentation URL for the specified package. -                         Used to build absolute paths that link to specific symbols. -        :param inventory_url: The absolute URL to the intersphinx inventory. -                              Fetched by running `intersphinx.fetch_inventory` in an -                              executor on the bot's event loop. -        :param config: A `SphinxConfiguration` instance to mock the regular sphinx -                       project layout. Required for use with intersphinx. +        Where: +            * `package_name` is the package name to use, appears in the log +            * `base_url` is the root documentation URL for the specified package, used to build +                         absolute paths that link to specific symbols +            * `inventory_url` is the absolute URL to the intersphinx inventory, fetched by running +                              `intersphinx.fetch_inventory` in an executor on the bot's event loop +            * `config` is a `SphinxConfiguration` instance to mock the regular sphinx +                       project layout, required for use with intersphinx          """ -          self.base_urls[package_name] = base_url          fetch_func = functools.partial(intersphinx.fetch_inventory, config, '', inventory_url) @@ -158,7 +158,8 @@ class Doc:          log.trace(f"Fetched inventory for {package_name}.") -    async def refresh_inventory(self): +    async def refresh_inventory(self) -> None: +        """Refresh internal documentation inventory."""          log.debug("Refreshing documentation inventory...")          # Clear the old base URLS and inventories to ensure @@ -185,16 +186,13 @@ class Doc:          """          Given a Python symbol, return its signature and description. -        :param symbol: The symbol for which HTML data should be returned. -        :return: -        A tuple in the form (str, str), or `None`. -        The first tuple element is the signature of the given -        symbol as a markup-free string, and the second tuple -        element is the description of the given symbol with HTML -        markup included. If the given symbol could not be found, -        returns `None`. -        """ +        Returns a tuple in the form (str, str), or `None`. +        The first tuple element is the signature of the given symbol as a markup-free string, and +        the second tuple element is the description of the given symbol with HTML markup included. + +        If the given symbol could not be found, returns `None`. +        """          url = self.inventories.get(symbol)          if url is None:              return None @@ -222,16 +220,10 @@ class Doc:      @async_cache(arg_offset=1)      async def get_symbol_embed(self, symbol: str) -> Optional[discord.Embed]:          """ -        Using `get_symbol_html`, attempt to scrape and -        fetch the data for the given `symbol`, and build -        a formatted embed out of its contents. - -        :param symbol: The symbol for which the embed should be returned -        :return: -        If the symbol is known, an Embed with documentation about it. -        Otherwise, `None`. -        """ +        Attempt to scrape and fetch the data for the given `symbol`, and build an embed from its contents. +        If the symbol is known, an Embed with documentation about it is returned. +        """          scraped_html = await self.get_symbol_html(symbol)          if scraped_html is None:              return None @@ -266,20 +258,16 @@ class Doc:          )      @commands.group(name='docs', aliases=('doc', 'd'), invoke_without_command=True) -    async def docs_group(self, ctx, symbol: commands.clean_content = None): +    async def docs_group(self, ctx: commands.Context, symbol: commands.clean_content = None) -> None:          """Lookup documentation for Python symbols.""" -          await ctx.invoke(self.get_command)      @docs_group.command(name='get', aliases=('g',)) -    async def get_command(self, ctx, symbol: commands.clean_content = None): +    async def get_command(self, ctx: commands.Context, symbol: commands.clean_content = None) -> None:          """          Return a documentation embed for a given symbol. -        If no symbol is given, return a list of all available inventories. -        :param ctx: Discord message context -        :param symbol: The symbol for which documentation should be returned, -                       or nothing to get a list of all inventories +        If no symbol is given, return a list of all available inventories.          Examples:              !docs @@ -287,7 +275,6 @@ class Doc:              !docs aiohttp.ClientSession              !docs get aiohttp.ClientSession          """ -          if symbol is None:              inventory_embed = discord.Embed(                  title=f"All inventories (`{len(self.base_urls)}` total)", @@ -321,18 +308,13 @@ class Doc:      @docs_group.command(name='set', aliases=('s',))      @with_role(*MODERATION_ROLES)      async def set_command( -        self, ctx, package_name: ValidPythonIdentifier, +        self, ctx: commands.Context, package_name: ValidPythonIdentifier,          base_url: ValidURL, inventory_url: InventoryURL -    ): +    ) -> None:          """          Adds a new documentation metadata object to the site's database. -        The database will update the object, should an existing item -        with the specified `package_name` already exist. -        :param ctx: Discord message context -        :param package_name: The package name, for example `aiohttp`. -        :param base_url: The package documentation's root URL, used to build absolute links. -        :param inventory_url: The intersphinx inventory URL. +        The database will update the object, should an existing item with the specified `package_name` already exist.          Example:              !docs set \ @@ -340,7 +322,6 @@ class Doc:                      https://discordpy.readthedocs.io/en/rewrite/ \                      https://discordpy.readthedocs.io/en/rewrite/objects.inv          """ -          body = {              'package': package_name,              'base_url': base_url, @@ -364,17 +345,13 @@ class Doc:      @docs_group.command(name='delete', aliases=('remove', 'rm', 'd'))      @with_role(*MODERATION_ROLES) -    async def delete_command(self, ctx, package_name: ValidPythonIdentifier): +    async def delete_command(self, ctx: commands.Context, package_name: ValidPythonIdentifier) -> None:          """          Removes the specified package from the database. -        :param ctx: Discord message context -        :param package_name: The package name, for example `aiohttp`. -          Examples:              !docs delete aiohttp          """ -          await self.bot.api_client.delete(f'bot/documentation-links/{package_name}')          async with ctx.typing(): @@ -384,5 +361,7 @@ class Doc:          await ctx.send(f"Successfully deleted `{package_name}` and refreshed inventory.") -def setup(bot): +def setup(bot: commands.Bot) -> None: +    """Doc cog load."""      bot.add_cog(Doc(bot)) +    log.info("Cog loaded: Doc") diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py index 2063df09d..f8a27fda8 100644 --- a/bot/cogs/error_handler.py +++ b/bot/cogs/error_handler.py @@ -24,7 +24,8 @@ class ErrorHandler:      def __init__(self, bot: Bot):          self.bot = bot -    async def on_command_error(self, ctx: Context, e: CommandError): +    async def on_command_error(self, ctx: Context, e: CommandError) -> None: +        """Provide command error handling."""          command = ctx.command          parent = None @@ -87,6 +88,7 @@ class ErrorHandler:              raise e -def setup(bot: Bot): +def setup(bot: Bot) -> None: +    """Error handler cog load."""      bot.add_cog(ErrorHandler(bot))      log.info("Cog loaded: Events") diff --git a/bot/cogs/eval.py b/bot/cogs/eval.py index 8e97a35a2..5fbd9dca5 100644 --- a/bot/cogs/eval.py +++ b/bot/cogs/eval.py @@ -6,6 +6,7 @@ import re  import textwrap  import traceback  from io import StringIO +from typing import Any, Tuple, Union  import discord  from discord.ext.commands import Bot, group @@ -18,10 +19,7 @@ log = logging.getLogger(__name__)  class CodeEval: -    """ -    Owner and admin feature that evaluates code -    and returns the result to the channel. -    """ +    """Owner and admin feature that evaluates code and returns the result to the channel."""      def __init__(self, bot: Bot):          self.bot = bot @@ -31,7 +29,8 @@ class CodeEval:          self.interpreter = Interpreter(bot) -    def _format(self, inp, out):  # (str, Any) -> (str, discord.Embed) +    def _format(self, inp: str, out: Any) -> Tuple[str, Union[discord.embed, None]]: +        """Format the eval output into a string & attempt to format it into an Embed."""          self._ = out          res = "" @@ -124,7 +123,8 @@ class CodeEval:          return res  # Return (text, embed) -    async def _eval(self, ctx, code):  # (discord.Context, str) -> None +    async def _eval(self, ctx: discord.Context, code: str) -> None: +        """Eval the input code string & send an embed to the invoking context."""          self.ln += 1          if code.startswith("exit"): @@ -174,16 +174,15 @@ async def func():  # (None,) -> Any      @group(name='internal', aliases=('int',))      @with_role(Roles.owner, Roles.admin) -    async def internal_group(self, ctx): +    async def internal_group(self, ctx: discord.Context) -> None:          """Internal commands. Top secret!""" -          if not ctx.invoked_subcommand:              await ctx.invoke(self.bot.get_command("help"), "internal")      @internal_group.command(name='eval', aliases=('e',))      @with_role(Roles.admin, Roles.owner) -    async def eval(self, ctx, *, code: str): -        """ Run eval in a REPL-like format. """ +    async def eval(self, ctx: discord.Context, *, code: str) -> None: +        """Run eval in a REPL-like format."""          code = code.strip("`")          if re.match('py(thon)?\n', code):              code = "\n".join(code.split("\n")[1:]) @@ -197,6 +196,7 @@ async def func():  # (None,) -> Any          await self._eval(ctx, code) -def setup(bot): +def setup(bot: Bot) -> None: +    """Code eval cog load."""      bot.add_cog(CodeEval(bot))      log.info("Cog loaded: Eval") diff --git a/bot/cogs/jams.py b/bot/cogs/jams.py index 96b98e559..f7a5896c0 100644 --- a/bot/cogs/jams.py +++ b/bot/cogs/jams.py @@ -10,9 +10,7 @@ log = logging.getLogger(__name__)  class CodeJams: -    """ -    Manages the code-jam related parts of our server -    """ +    """Manages the code-jam related parts of our server."""      def __init__(self, bot: commands.Bot):          self.bot = bot @@ -22,14 +20,12 @@ class CodeJams:      async def createteam(          self, ctx: commands.Context,          team_name: str, members: commands.Greedy[Member] -    ): +    ) -> None:          """ -        Create a team channel (both voice and text) in the Code Jams category, assign roles -        and then add overwrites for the team. +        Create team channels (voice and text) in the Code Jams category, assign roles, and add overwrites for the team.          The first user passed will always be the team leader.          """ -          # We had a little issue during Code Jam 4 here, the greedy converter did it's job          # and ignored anything which wasn't a valid argument which left us with teams of          # two members or at some times even 1 member. This fixes that by checking that there @@ -105,6 +101,7 @@ class CodeJams:          await ctx.send(f":ok_hand: Team created: {team_channel.mention}") -def setup(bot): +def setup(bot: commands.Bot) -> None: +    """Code Jams cog load."""      bot.add_cog(CodeJams(bot))      log.info("Cog loaded: CodeJams") diff --git a/bot/cogs/site.py b/bot/cogs/site.py index 37bf4f4ea..a941f27a7 100644 --- a/bot/cogs/site.py +++ b/bot/cogs/site.py @@ -19,15 +19,13 @@ class Site:          self.bot = bot      @group(name="site", aliases=("s",), invoke_without_command=True) -    async def site_group(self, ctx): +    async def site_group(self, ctx: Context) -> None:          """Commands for getting info about our website.""" -          await ctx.invoke(self.bot.get_command("help"), "site")      @site_group.command(name="home", aliases=("about",)) -    async def site_main(self, ctx: Context): +    async def site_main(self, ctx: Context) -> None:          """Info about the website itself.""" -          url = f"{URLs.site_schema}{URLs.site}/"          embed = Embed(title="Python Discord website") @@ -43,9 +41,8 @@ class Site:          await ctx.send(embed=embed)      @site_group.command(name="resources") -    async def site_resources(self, ctx: Context): +    async def site_resources(self, ctx: Context) -> None:          """Info about the site's Resources page.""" -          url = f"{INFO_URL}/resources"          embed = Embed(title="Resources") @@ -60,9 +57,8 @@ class Site:          await ctx.send(embed=embed)      @site_group.command(name="help") -    async def site_help(self, ctx: Context): +    async def site_help(self, ctx: Context) -> None:          """Info about the site's Getting Help page.""" -          url = f"{INFO_URL}/help"          embed = Embed(title="Getting Help") @@ -77,9 +73,8 @@ class Site:          await ctx.send(embed=embed)      @site_group.command(name="faq") -    async def site_faq(self, ctx: Context): +    async def site_faq(self, ctx: Context) -> None:          """Info about the site's FAQ page.""" -          url = f"{INFO_URL}/faq"          embed = Embed(title="FAQ") @@ -96,14 +91,8 @@ class Site:      @site_group.command(aliases=['r', 'rule'], name='rules')      @redirect_output(destination_channel=Channels.bot, bypass_roles=STAFF_ROLES) -    async def site_rules(self, ctx: Context, *rules: int): -        """ -        Provides a link to the `rules` endpoint of the website, or displays -        specific rules, if they are requested. - -        **`ctx`:** The Discord message context -        **`rules`:** The rules a user wants to get. -        """ +    async def site_rules(self, ctx: Context, *rules: int) -> None: +        """Provides a link to the `rules` endpoint of the website, or displays specific rule(s), if requested."""          rules_embed = Embed(title='Rules', color=Colour.blurple())          rules_embed.url = f"{URLs.site_schema}{URLs.site}/about/rules" @@ -135,6 +124,7 @@ class Site:          await LinePaginator.paginate(final_rules, ctx, rules_embed, max_lines=3) -def setup(bot): +def setup(bot: Bot) -> None: +    """Site cog load."""      bot.add_cog(Site(bot))      log.info("Cog loaded: Site") diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 0c6d9d2ba..09e8f70d6 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -6,7 +6,7 @@ from email.parser import HeaderParser  from io import StringIO  from discord import Colour, Embed -from discord.ext.commands import AutoShardedBot, Context, command +from discord.ext.commands import Bot, CheckFailure, Context, command  from bot.constants import Channels, NEGATIVE_REPLIES, STAFF_ROLES  from bot.decorators import InChannelCheckFailure, in_channel @@ -15,22 +15,17 @@ log = logging.getLogger(__name__)  class Utils: -    """ -    A selection of utilities which don't have a clear category. -    """ +    """A selection of utilities which don't have a clear category.""" -    def __init__(self, bot: AutoShardedBot): +    def __init__(self, bot: Bot):          self.bot = bot          self.base_pep_url = "http://www.python.org/dev/peps/pep-"          self.base_github_pep_url = "https://raw.githubusercontent.com/python/peps/master/pep-"      @command(name='pep', aliases=('get_pep', 'p')) -    async def pep_command(self, ctx: Context, pep_number: str): -        """ -        Fetches information about a PEP and sends it to the channel. -        """ - +    async def pep_command(self, ctx: Context, pep_number: str) -> None: +        """Fetches information about a PEP and sends it to the channel."""          if pep_number.isdigit():              pep_number = int(pep_number)          else: @@ -90,11 +85,8 @@ class Utils:      @command()      @in_channel(Channels.bot, bypass_roles=STAFF_ROLES) -    async def charinfo(self, ctx, *, characters: str): -        """ -        Shows you information on up to 25 unicode characters. -        """ - +    async def charinfo(self, ctx: Context, *, characters: str) -> None: +        """Shows you information on up to 25 unicode characters."""          match = re.match(r"<(a?):(\w+):(\d+)>", characters)          if match:              embed = Embed( @@ -133,7 +125,8 @@ class Utils:          await ctx.send(embed=embed) -    async def __error(self, ctx, error): +    async def __error(self, ctx: Context, error: CheckFailure) -> None: +        """Send Check failure error to invoking context if command is invoked in a blacklisted channel by non-staff."""          embed = Embed(colour=Colour.red())          if isinstance(error, InChannelCheckFailure):              embed.title = random.choice(NEGATIVE_REPLIES) @@ -141,6 +134,7 @@ class Utils:              await ctx.send(embed=embed) -def setup(bot): +def setup(bot: Bot) -> None: +    """Utils cog load."""      bot.add_cog(Utils(bot))      log.info("Cog loaded: Utils") | 
