From f799d03756e4dd61c9407baa552409adc482e212 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sun, 18 Aug 2019 10:05:54 +0800 Subject: Add utils function to replace multiple words in a given string --- bot/utils/__init__.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) (limited to 'bot/utils/__init__.py') diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index ef18a1b9..ad019357 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -1,4 +1,6 @@ import asyncio +import re +import string from typing import List import discord @@ -77,3 +79,57 @@ async def disambiguate( return entries[index - 1] except IndexError: raise BadArgument('Invalid choice.') + + +def replace_many( + sentence: str, replacements: dict, *, ignore_case: bool = False, match_case: bool = False +) -> str: + """ + Replaces multiple substrings in a string given a mapping of strings. + + By default replaces long strings before short strings, and lowercase before uppercase. + Example: + var = replace_many("This is a sentence", {"is": "was", "This": "That"}) + assert var == "That was a sentence" + + If `ignore_case` is given, does a case insensitive match. + Example: + var = replace_many("THIS is a sentence", {"IS": "was", "tHiS": "That"}, ignore_case=True) + assert var == "That was a sentence" + + If `match_case` is given, matches the case of the replacement with the replaced word. + Example: + var = replace_many( + "This IS a sentence", {"is": "was", "this": "that"}, ignore_case=True, match_case=True + ) + assert var == "That WAS a sentence" + """ + if ignore_case: + replacements = dict( + (word.lower(), replacement) for word, replacement in replacements.items() + ) + + words_to_replace = sorted(replacements, key=lambda s: (-len(s), s)) + + # Join and compile words to replace into a regex + pattern = "|".join(re.escape(word) for word in words_to_replace) + regex = re.compile(pattern, re.I if ignore_case else 0) + + def _repl(match): + """Returns replacement depending on `ignore_case` and `match_case`""" + word = match.group(0) + replacement = replacements[word.lower() if ignore_case else word] + + if not match_case: + return replacement + + # Clean punctuation from word so string methods work + cleaned_word = word.translate(str.maketrans('', '', string.punctuation)) + if cleaned_word.isupper(): + return replacement.upper() + elif cleaned_word[0].isupper(): + return replacement.capitalize() + else: + return replacement.lower() + + return regex.sub(_repl, sentence) -- cgit v1.2.3 From fd6e975d6dfe587a534b081c7853f04201f22ed4 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Mon, 19 Aug 2019 20:10:48 +0800 Subject: Replace paramater and return value docstring documentation with an extended summary --- bot/pagination.py | 83 +++++++----------------------- bot/seasons/evergreen/snakes/snakes_cog.py | 18 ++----- bot/seasons/evergreen/snakes/utils.py | 16 +----- bot/seasons/halloween/monstersurvey.py | 17 ++---- bot/seasons/valentines/be_my_valentine.py | 5 +- bot/utils/__init__.py | 16 ++---- 6 files changed, 34 insertions(+), 121 deletions(-) (limited to 'bot/utils/__init__.py') diff --git a/bot/pagination.py b/bot/pagination.py index e6cea41f..212f9f4e 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -22,26 +22,16 @@ class EmptyPaginatorEmbed(Exception): class LinePaginator(Paginator): - """ - A class that aids in paginating code blocks for Discord messages. - - Attributes - ----------- - prefix: :class:`str` - The prefix inserted to every page. e.g. three backticks. - suffix: :class:`str` - The suffix appended at the end of every page. e.g. three backticks. - max_size: :class:`int` - The maximum amount of codepoints allowed in a page. - max_lines: :class:`int` - The maximum amount of lines allowed in a page. - """ + """A class that aids in paginating code blocks for Discord messages.""" def __init__(self, prefix='```', suffix='```', max_size=2000, max_lines=None): """ Overrides the Paginator.__init__ from inside discord.ext.commands. - Allows for configuration of the maximum number of lines per page. + `prefix` and `suffix` will be prepended and appended respectively to every page. + + `max_size` and `max_lines` denote the maximum amount of codepoints and lines + allowed per page. """ self.prefix = prefix self.suffix = suffix @@ -56,22 +46,12 @@ class LinePaginator(Paginator): """ Adds a line to the current page. - If the line exceeds the `max_size` then an exception is raised. + If the line exceeds the `max_size` then a RuntimeError is raised. Overrides the Paginator.add_line from inside discord.ext.commands in order to allow configuration of the maximum number of lines per page. - Parameters - ----------- - line: str - The line to add. - empty: bool - Indicates if another empty line should be added. - - Raises - ------ - RuntimeError - The line was too big for the current `max_size`. + If `empty` is True, an empty line will be placed after the a given `line`. """ if len(line) > self.max_size - len(self.prefix) - 2: raise RuntimeError('Line exceeds maximum page size %s' % (self.max_size - len(self.prefix) - 2)) @@ -105,7 +85,11 @@ class LinePaginator(Paginator): When used, this will send a message using `ctx.send()` and apply a set of reactions to it. These reactions may be used to change page, or to remove pagination from the message. - Pagination will also be removed automatically if no reaction is added for five minutes (300 seconds). + + Pagination will also be removed automatically if no reaction is added for `timeout` seconds, + defaulting to five minutes (300 seconds). + + If `empty` is True, an empty line will be placed between each given line. >>> embed = Embed() >>> embed.set_author(name="Some Operation", url=url, icon_url=icon) @@ -113,20 +97,6 @@ class LinePaginator(Paginator): ... (line for line in lines), ... ctx, embed ... ) - - :param lines: The lines to be paginated - :param ctx: Current context object - :param embed: A pre-configured embed to be used as a template for each page - :param prefix: Text to place before each page - :param suffix: Text to place after each page - :param max_lines: The maximum number of lines on each page - :param max_size: The maximum number of characters on each page - :param empty: Whether to place an empty line between each given line - :param restrict_to_user: A user to lock pagination operations to for this message, if supplied - :param exception_on_empty_embed: Should there be an exception if the embed is empty? - :param url: the url to use for the embed headline - :param timeout: The amount of time in seconds to disable pagination of no reaction is added - :param footer_text: Text to prefix the page number in the footer with """ def event_check(reaction_: Reaction, user_: Member): """Make sure that this reaction is what we want to operate on.""" @@ -314,8 +284,7 @@ class ImagePaginator(Paginator): """ Adds a line to each page, usually just 1 line in this context. - :param line: str to be page content / title - :param empty: if there should be new lines between entries + If `empty` is True, an empty line will be placed after a given `line`. """ if line: self._count = len(line) @@ -326,9 +295,7 @@ class ImagePaginator(Paginator): def add_image(self, image: str = None) -> None: """ - Adds an image to a page. - - :param image: image url to be appended + Adds an image to a page given the url. """ self.images.append(image) @@ -339,33 +306,21 @@ class ImagePaginator(Paginator): """ Use a paginator and set of reactions to provide pagination over a set of title/image pairs. - The reactions are used to switch page, or to finish with pagination. + `pages` is a list of tuples of page title/image url pairs. + `prefix` and `suffix` will be prepended and appended respectively to the message. When used, this will send a message using `ctx.send()` and apply a set of reactions to it. These reactions may be used to change page, or to remove pagination from the message. - Note: Pagination will be removed automatically if no reaction is added for five minutes (300 seconds). + Note: Pagination will be removed automatically if no reaction is added for `timeout` seconds, + defaulting to five minutes (300 seconds). >>> embed = Embed() >>> embed.set_author(name="Some Operation", url=url, icon_url=icon) >>> await ImagePaginator.paginate(pages, ctx, embed) - - Parameters - ----------- - :param pages: An iterable of tuples with title for page, and img url - :param ctx: ctx for message - :param embed: base embed to modify - :param prefix: prefix of message - :param suffix: suffix of message - :param timeout: timeout for when reactions get auto-removed """ def check_event(reaction_: Reaction, member: Member) -> bool: - """ - Checks each reaction added, if it matches our conditions pass the wait_for. - - :param reaction_: reaction added - :param member: reaction added by member - """ + """Checks each reaction added, if it matches our conditions pass the wait_for.""" return all(( # Reaction is on the same message sent reaction_.message.id == message.id, diff --git a/bot/seasons/evergreen/snakes/snakes_cog.py b/bot/seasons/evergreen/snakes/snakes_cog.py index 1d138aff..6924a552 100644 --- a/bot/seasons/evergreen/snakes/snakes_cog.py +++ b/bot/seasons/evergreen/snakes/snakes_cog.py @@ -303,9 +303,6 @@ class Snakes(Cog): Builds a dict that the .get() method can use. Created by Ava and eivl. - - :param name: The name of the snake to get information for - omit for a random snake - :return: A dict containing information on a snake """ snake_info = {} @@ -631,14 +628,10 @@ class Snakes(Cog): @snakes_group.command(name='get') @bot_has_permissions(manage_messages=True) @locked() - async def get_command(self, ctx: Context, *, name: Snake = None): + async def get_command(self, ctx: Context, *, name: Snake = None) -> None: """ Fetches information about a snake from Wikipedia. - :param ctx: Context object passed from discord.py - :param name: Optional, the name of the snake to get information - for - omit for a random snake - Created by Ava and eivl. """ with ctx.typing(): @@ -1034,10 +1027,8 @@ class Snakes(Cog): """ How would I talk if I were a snake? - :param ctx: context - :param message: If this is passed, it will snakify the message. - If not, it will snakify a random message from - the users history. + If `message` is passed, the bot will snakify the message. + Otherwise, a random message from the user's history is snakified. Written by Momo and kel. Modified by lemon. @@ -1077,8 +1068,7 @@ class Snakes(Cog): """ Gets a YouTube video about snakes. - :param ctx: Context object passed from discord.py - :param search: Optional, a name of a snake. Used to search for videos with that name + If `search` is given, a snake with that name will be searched on Youtube. Written by Andrew and Prithaj. """ diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index 5d3b0dee..e8d2ee44 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -285,20 +285,8 @@ def create_snek_frame( """ Creates a single random snek frame using Perlin noise. - :param perlin_factory: the perlin noise factory used. Required. - :param perlin_lookup_vertical_shift: the Perlin noise shift in the Y-dimension for this frame - :param image_dimensions: the size of the output image. - :param image_margins: the margins to respect inside of the image. - :param snake_length: the length of the snake, in segments. - :param snake_color: the color of the snake. - :param bg_color: the background color. - :param segment_length_range: the range of the segment length. Values will be generated inside - this range, including the bounds. - :param snake_width: the width of the snek, in pixels. - :param text: the text to display with the snek. Set to None for no text. - :param text_position: the position of the text. - :param text_color: the color of the text. - :return: a PIL image, representing a single frame. + `perlin_lookup_vertical_shift` represents the Perlin noise shift in the Y-dimension for this frame. + If `text` is given, display the given text with the snek. """ start_x = random.randint(image_margins[X], image_dimensions[X] - image_margins[X]) start_y = random.randint(image_margins[Y], image_dimensions[Y] - image_margins[Y]) diff --git a/bot/seasons/halloween/monstersurvey.py b/bot/seasons/halloween/monstersurvey.py index 4e967cca..05e86628 100644 --- a/bot/seasons/halloween/monstersurvey.py +++ b/bot/seasons/halloween/monstersurvey.py @@ -36,15 +36,11 @@ class MonsterSurvey(Cog): with open(self.registry_location, 'w') as jason: json.dump(self.voter_registry, jason, indent=2) - def cast_vote(self, id: int, monster: str): + def cast_vote(self, id: int, monster: str) -> None: """ Cast a user's vote for the specified monster. If the user has already voted, their existing vote is removed. - - :param id: The id of the person voting - :param monster: the string key of the json that represents a monster - :return: None """ vr = self.voter_registry for m in vr.keys(): @@ -147,13 +143,9 @@ class MonsterSurvey(Cog): @monster_group.command( name='show' ) - async def monster_show(self, ctx: Context, name=None): + async def monster_show(self, ctx: Context, name=None) -> None: """ Shows the named monster. If one is not named, it sends the default voting embed instead. - - :param ctx: - :param name: - :return: """ if name is None: await ctx.invoke(self.monster_leaderboard) @@ -184,12 +176,9 @@ class MonsterSurvey(Cog): name='leaderboard', aliases=('lb',) ) - async def monster_leaderboard(self, ctx: Context): + async def monster_leaderboard(self, ctx: Context) -> None: """ Shows the current standings. - - :param ctx: - :return: """ async with ctx.typing(): vr = self.voter_registry diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index ac140896..c4acf17a 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -184,14 +184,11 @@ class BeMyValentine(commands.Cog): return valentine, title @staticmethod - def random_user(author, members): + def random_user(author: discord.Member, members: discord.Member): """ Picks a random member from the list provided in `members`. The invoking author is ignored. - - :param author: member who invoked the command - :param members: list of discord.Member objects """ if author in members: members.remove(author) diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index ef18a1b9..15c4b5db 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -9,21 +9,15 @@ from bot.pagination import LinePaginator async def disambiguate( ctx: Context, entries: List[str], *, timeout: float = 30, - per_page: int = 20, empty: bool = False, embed: discord.Embed = None -): + entries_per_page: int = 20, empty: bool = False, embed: discord.Embed = None +) -> str: """ Has the user choose between multiple entries in case one could not be chosen automatically. + Disambiguation will be canceled after `timeout` seconds. + This will raise a BadArgument if entries is empty, if the disambiguation event times out, or if the user makes an invalid choice. - - :param ctx: Context object from discord.py - :param entries: List of items for user to choose from - :param timeout: Number of seconds to wait before canceling disambiguation - :param per_page: Entries per embed page - :param empty: Whether the paginator should have an extra line between items - :param embed: The embed that the paginator will use. - :return: Users choice for correct entry. """ if len(entries) == 0: raise BadArgument('No matches found.') @@ -43,7 +37,7 @@ async def disambiguate( embed = discord.Embed() coro1 = ctx.bot.wait_for('message', check=check, timeout=timeout) - coro2 = LinePaginator.paginate(choices, ctx, embed=embed, max_lines=per_page, + coro2 = LinePaginator.paginate(choices, ctx, embed=embed, max_lines=entries_per_page, empty=empty, max_size=6000, timeout=9000) # wait_for timeout will go to except instead of the wait_for thing as I expected -- cgit v1.2.3 From 4b18d7e430d5cea16406c65349718f72919c01c3 Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Mon, 9 Sep 2019 16:37:13 -0400 Subject: Lint remaining files hacktoberstats cog handled in separate PR --- bot/__init__.py | 2 +- bot/decorators.py | 6 +++--- bot/seasons/pride/pride_anthem.py | 6 +++--- bot/seasons/pride/pride_avatar.py | 12 +++++------ bot/seasons/valentines/be_my_valentine.py | 34 +++++++++++++++++------------- bot/seasons/valentines/lovecalculator.py | 6 +++--- bot/seasons/valentines/movie_generator.py | 6 +++--- bot/seasons/valentines/myvalenstate.py | 8 +++---- bot/seasons/valentines/pickuplines.py | 6 +++--- bot/seasons/valentines/savethedate.py | 6 +++--- bot/seasons/valentines/valentine_zodiac.py | 8 +++---- bot/seasons/valentines/whoisvalentine.py | 8 +++---- bot/utils/__init__.py | 4 ++-- bot/utils/halloween/spookifications.py | 8 +++---- 14 files changed, 62 insertions(+), 58 deletions(-) (limited to 'bot/utils/__init__.py') diff --git a/bot/__init__.py b/bot/__init__.py index 8950423f..4729e50c 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -13,7 +13,7 @@ logging.TRACE = 5 logging.addLevelName(logging.TRACE, "TRACE") -def monkeypatch_trace(self, msg: str, *args, **kwargs) -> None: +def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None: """ Log 'msg % args' with severity 'TRACE'. diff --git a/bot/decorators.py b/bot/decorators.py index e12c3f34..dbaad4a2 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -117,7 +117,7 @@ def override_in_channel(func: typing.Callable) -> typing.Callable: return func -def locked(): +def locked() -> typing.Union[typing.Callable, None]: """ Allows the user to only run one instance of the decorated command at a time. @@ -125,11 +125,11 @@ def locked(): This decorator has to go before (below) the `command` decorator. """ - def wrap(func: typing.Callable): + def wrap(func: typing.Callable) -> typing.Union[typing.Callable, None]: func.__locks = WeakValueDictionary() @wraps(func) - async def inner(self, ctx: Context, *args, **kwargs): + async def inner(self: typing.Callable, ctx: Context, *args, **kwargs) -> typing.Union[typing.Callable, None]: lock = func.__locks.setdefault(ctx.author.id, Lock()) if lock.locked(): embed = Embed() diff --git a/bot/seasons/pride/pride_anthem.py b/bot/seasons/pride/pride_anthem.py index f226f4bb..b0c6d34e 100644 --- a/bot/seasons/pride/pride_anthem.py +++ b/bot/seasons/pride/pride_anthem.py @@ -11,7 +11,7 @@ log = logging.getLogger(__name__) class PrideAnthem(commands.Cog): """Embed a random youtube video for a gay anthem!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.anthems = self.load_vids() @@ -39,7 +39,7 @@ class PrideAnthem(commands.Cog): return anthems @commands.command(name="prideanthem", aliases=["anthem", "pridesong"]) - async def prideanthem(self, ctx, genre: str = None): + async def prideanthem(self, ctx: commands.Context, genre: str = None) -> None: """ Sends a message with a video of a random pride anthem. @@ -52,7 +52,7 @@ class PrideAnthem(commands.Cog): await ctx.send("I couldn't find a video, sorry!") -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog loader for pride anthem.""" bot.add_cog(PrideAnthem(bot)) log.info("Pride anthems cog loaded!") diff --git a/bot/seasons/pride/pride_avatar.py b/bot/seasons/pride/pride_avatar.py index a5b38d20..85e49d5c 100644 --- a/bot/seasons/pride/pride_avatar.py +++ b/bot/seasons/pride/pride_avatar.py @@ -56,11 +56,11 @@ OPTIONS = { class PrideAvatar(commands.Cog): """Put an LGBT spin on your avatar!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - def crop_avatar(avatar): + def crop_avatar(avatar: Image) -> Image: """This crops the avatar into a circle.""" mask = Image.new("L", avatar.size, 0) draw = ImageDraw.Draw(mask) @@ -69,7 +69,7 @@ class PrideAvatar(commands.Cog): return avatar @staticmethod - def crop_ring(ring, px): + def crop_ring(ring: Image, px: int) -> Image: """This crops the ring into a circle.""" mask = Image.new("L", ring.size, 0) draw = ImageDraw.Draw(mask) @@ -79,7 +79,7 @@ class PrideAvatar(commands.Cog): return ring @commands.group(aliases=["avatarpride", "pridepfp", "prideprofile"], invoke_without_command=True) - async def prideavatar(self, ctx, option="lgbt", pixels: int = 64): + async def prideavatar(self, ctx: commands.Context, option: str = "lgbt", pixels: int = 64) -> None: """ This surrounds an avatar with a border of a specified LGBT flag. @@ -126,7 +126,7 @@ class PrideAvatar(commands.Cog): await ctx.send(file=file, embed=embed) @prideavatar.command() - async def flags(self, ctx): + async def flags(self, ctx: commands.Context) -> None: """This lists the flags that can be used with the prideavatar command.""" choices = sorted(set(OPTIONS.values())) options = "• " + "\n• ".join(choices) @@ -139,7 +139,7 @@ class PrideAvatar(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Cog load.""" bot.add_cog(PrideAvatar(bot)) log.info("PrideAvatar cog loaded") diff --git a/bot/seasons/valentines/be_my_valentine.py b/bot/seasons/valentines/be_my_valentine.py index c4acf17a..a073e1bd 100644 --- a/bot/seasons/valentines/be_my_valentine.py +++ b/bot/seasons/valentines/be_my_valentine.py @@ -1,8 +1,8 @@ import logging import random -import typing from json import load from pathlib import Path +from typing import Optional, Tuple import discord from discord.ext import commands @@ -18,12 +18,12 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea class BeMyValentine(commands.Cog): """A cog that sends Valentines to other users!""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.valentines = self.load_json() @staticmethod - def load_json(): + def load_json() -> dict: """Load Valentines messages from the static resources.""" p = Path("bot/resources/valentines/bemyvalentine_valentines.json") with p.open() as json_data: @@ -31,7 +31,7 @@ class BeMyValentine(commands.Cog): return valentines @commands.group(name="lovefest", invoke_without_command=True) - async def lovefest_role(self, ctx): + async def lovefest_role(self, ctx: commands.Context) -> None: """ Subscribe or unsubscribe from the lovefest role. @@ -43,7 +43,7 @@ class BeMyValentine(commands.Cog): await ctx.send_help(ctx.command) @lovefest_role.command(name="sub") - async def add_role(self, ctx): + async def add_role(self, ctx: commands.Context) -> None: """Adds the lovefest role.""" user = ctx.author role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) @@ -54,7 +54,7 @@ class BeMyValentine(commands.Cog): await ctx.send("You already have the role !") @lovefest_role.command(name="unsub") - async def remove_role(self, ctx): + async def remove_role(self, ctx: commands.Context) -> None: """Removes the lovefest role.""" user = ctx.author role = discord.utils.get(ctx.guild.roles, id=Lovefest.role_id) @@ -66,7 +66,9 @@ class BeMyValentine(commands.Cog): @commands.cooldown(1, 1800, BucketType.user) @commands.group(name='bemyvalentine', invoke_without_command=True) - async def send_valentine(self, ctx, user: typing.Optional[discord.Member] = None, *, valentine_type=None): + async def send_valentine( + self, ctx: commands.Context, user: Optional[discord.Member] = None, *, valentine_type: str = None + ) -> None: """ Send a valentine to user, if specified, or to a random user with the lovefest role. @@ -112,7 +114,9 @@ class BeMyValentine(commands.Cog): @commands.cooldown(1, 1800, BucketType.user) @send_valentine.command(name='secret') - async def anonymous(self, ctx, user: typing.Optional[discord.Member] = None, *, valentine_type=None): + async def anonymous( + self, ctx: commands.Context, user: Optional[discord.Member] = None, *, valentine_type: str = None + ) -> None: """ Send an anonymous Valentine via DM to to a user, if specified, or to a random with the lovefest role. @@ -164,7 +168,7 @@ class BeMyValentine(commands.Cog): else: await ctx.author.send(f"Your message has been sent to {user}") - def valentine_check(self, valentine_type): + def valentine_check(self, valentine_type: str) -> Tuple[str, str]: """Return the appropriate Valentine type & title based on the invoking user's input.""" if valentine_type is None: valentine, title = self.random_valentine() @@ -184,7 +188,7 @@ class BeMyValentine(commands.Cog): return valentine, title @staticmethod - def random_user(author: discord.Member, members: discord.Member): + def random_user(author: discord.Member, members: discord.Member) -> None: """ Picks a random member from the list provided in `members`. @@ -196,13 +200,13 @@ class BeMyValentine(commands.Cog): return random.choice(members) if members else None @staticmethod - def random_emoji(): + def random_emoji() -> Tuple[str, str]: """Return two random emoji from the module-defined constants.""" EMOJI_1 = random.choice(HEART_EMOJIS) EMOJI_2 = random.choice(HEART_EMOJIS) return EMOJI_1, EMOJI_2 - def random_valentine(self): + def random_valentine(self) -> Tuple[str, str]: """Grabs a random poem or a compliment (any message).""" valentine_poem = random.choice(self.valentines['valentine_poems']) valentine_compliment = random.choice(self.valentines['valentine_compliments']) @@ -213,18 +217,18 @@ class BeMyValentine(commands.Cog): title = 'A compliment for ' return random_valentine, title - def valentine_poem(self): + def valentine_poem(self) -> str: """Grabs a random poem.""" valentine_poem = random.choice(self.valentines['valentine_poems']) return valentine_poem - def valentine_compliment(self): + def valentine_compliment(self) -> str: """Grabs a random compliment.""" valentine_compliment = random.choice(self.valentines['valentine_compliments']) return valentine_compliment -def setup(bot): +def setup(bot: commands.Bot) -> None: """Be my Valentine Cog load.""" bot.add_cog(BeMyValentine(bot)) log.info("BeMyValentine cog loaded") diff --git a/bot/seasons/valentines/lovecalculator.py b/bot/seasons/valentines/lovecalculator.py index 1d5a028d..207ef557 100644 --- a/bot/seasons/valentines/lovecalculator.py +++ b/bot/seasons/valentines/lovecalculator.py @@ -23,12 +23,12 @@ with Path("bot/resources/valentines/love_matches.json").open() as file: class LoveCalculator(Cog): """A cog for calculating the love between two people.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('love_calculator', 'love_calc')) @commands.cooldown(rate=1, per=5, type=commands.BucketType.user) - async def love(self, ctx, who: Union[Member, str], whom: Union[Member, str] = None): + async def love(self, ctx: commands.Context, who: Union[Member, str], whom: Union[Member, str] = None) -> None: """ Tells you how much the two love each other. @@ -98,7 +98,7 @@ class LoveCalculator(Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Love calculator Cog load.""" bot.add_cog(LoveCalculator(bot)) log.info("LoveCalculator cog loaded") diff --git a/bot/seasons/valentines/movie_generator.py b/bot/seasons/valentines/movie_generator.py index fa5f236a..ce1d7d5b 100644 --- a/bot/seasons/valentines/movie_generator.py +++ b/bot/seasons/valentines/movie_generator.py @@ -14,11 +14,11 @@ log = logging.getLogger(__name__) class RomanceMovieFinder(commands.Cog): """A Cog that returns a random romance movie suggestion to a user.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(name="romancemovie") - async def romance_movie(self, ctx): + async def romance_movie(self, ctx: commands.Context) -> None: """Randomly selects a romance movie and displays information about it.""" # Selecting a random int to parse it to the page parameter random_page = random.randint(0, 20) @@ -57,7 +57,7 @@ class RomanceMovieFinder(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Romance movie Cog load.""" bot.add_cog(RomanceMovieFinder(bot)) log.info("RomanceMovieFinder cog loaded") diff --git a/bot/seasons/valentines/myvalenstate.py b/bot/seasons/valentines/myvalenstate.py index fad202e3..0256c39a 100644 --- a/bot/seasons/valentines/myvalenstate.py +++ b/bot/seasons/valentines/myvalenstate.py @@ -18,10 +18,10 @@ with open(Path("bot/resources/valentines/valenstates.json"), "r") as file: class MyValenstate(commands.Cog): """A Cog to find your most likely Valentine's vacation destination.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot - def levenshtein(self, source, goal): + def levenshtein(self, source: str, goal: str) -> int: """Calculates the Levenshtein Distance between source and goal.""" if len(source) < len(goal): return self.levenshtein(goal, source) @@ -42,7 +42,7 @@ class MyValenstate(commands.Cog): return pre_row[-1] @commands.command() - async def myvalenstate(self, ctx, *, name=None): + async def myvalenstate(self, ctx: commands.Context, *, name: str = None) -> None: """Find the vacation spot(s) with the most matching characters to the invoking user.""" eq_chars = collections.defaultdict(int) if name is None: @@ -81,7 +81,7 @@ class MyValenstate(commands.Cog): await ctx.channel.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Valenstate Cog load.""" bot.add_cog(MyValenstate(bot)) log.info("MyValenstate cog loaded") diff --git a/bot/seasons/valentines/pickuplines.py b/bot/seasons/valentines/pickuplines.py index 46772197..8b2c9822 100644 --- a/bot/seasons/valentines/pickuplines.py +++ b/bot/seasons/valentines/pickuplines.py @@ -17,11 +17,11 @@ with open(Path("bot/resources/valentines/pickup_lines.json"), "r", encoding="utf class PickupLine(commands.Cog): """A cog that gives random cheesy pickup lines.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command() - async def pickupline(self, ctx): + async def pickupline(self, ctx: commands.Context) -> None: """ Gives you a random pickup line. @@ -39,7 +39,7 @@ class PickupLine(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Pickup lines Cog load.""" bot.add_cog(PickupLine(bot)) log.info('PickupLine cog loaded') diff --git a/bot/seasons/valentines/savethedate.py b/bot/seasons/valentines/savethedate.py index 34264183..e0bc3904 100644 --- a/bot/seasons/valentines/savethedate.py +++ b/bot/seasons/valentines/savethedate.py @@ -19,11 +19,11 @@ with open(Path("bot/resources/valentines/date_ideas.json"), "r", encoding="utf8" class SaveTheDate(commands.Cog): """A cog that gives random suggestion for a Valentine's date.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command() - async def savethedate(self, ctx): + async def savethedate(self, ctx: commands.Context) -> None: """Gives you ideas for what to do on a date with your valentine.""" random_date = random.choice(VALENTINES_DATES['ideas']) emoji_1 = random.choice(HEART_EMOJIS) @@ -36,7 +36,7 @@ class SaveTheDate(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Save the date Cog Load.""" bot.add_cog(SaveTheDate(bot)) log.info("SaveTheDate cog loaded") diff --git a/bot/seasons/valentines/valentine_zodiac.py b/bot/seasons/valentines/valentine_zodiac.py index fa849cb2..c8d77e75 100644 --- a/bot/seasons/valentines/valentine_zodiac.py +++ b/bot/seasons/valentines/valentine_zodiac.py @@ -17,12 +17,12 @@ HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_hea class ValentineZodiac(commands.Cog): """A Cog that returns a counter compatible zodiac sign to the given user's zodiac sign.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.zodiacs = self.load_json() @staticmethod - def load_json(): + def load_json() -> dict: """Load zodiac compatibility from static JSON resource.""" p = Path("bot/resources/valentines/zodiac_compatibility.json") with p.open() as json_data: @@ -30,7 +30,7 @@ class ValentineZodiac(commands.Cog): return zodiacs @commands.command(name="partnerzodiac") - async def counter_zodiac(self, ctx, zodiac_sign): + async def counter_zodiac(self, ctx: commands.Context, zodiac_sign: str) -> None: """Provides a counter compatible zodiac sign to the given user's zodiac sign.""" try: compatible_zodiac = random.choice(self.zodiacs[zodiac_sign.lower()]) @@ -52,7 +52,7 @@ class ValentineZodiac(commands.Cog): await ctx.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Valentine zodiac Cog load.""" bot.add_cog(ValentineZodiac(bot)) log.info("ValentineZodiac cog loaded") diff --git a/bot/seasons/valentines/whoisvalentine.py b/bot/seasons/valentines/whoisvalentine.py index d73ccd9b..b8586dca 100644 --- a/bot/seasons/valentines/whoisvalentine.py +++ b/bot/seasons/valentines/whoisvalentine.py @@ -17,11 +17,11 @@ with open(Path("bot/resources/valentines/valentine_facts.json"), "r") as file: class ValentineFacts(commands.Cog): """A Cog for displaying facts about Saint Valentine.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot @commands.command(aliases=('whoisvalentine', 'saint_valentine')) - async def who_is_valentine(self, ctx): + async def who_is_valentine(self, ctx: commands.Context) -> None: """Displays info about Saint Valentine.""" embed = discord.Embed( title="Who is Saint Valentine?", @@ -36,7 +36,7 @@ class ValentineFacts(commands.Cog): await ctx.channel.send(embed=embed) @commands.command() - async def valentine_fact(self, ctx): + async def valentine_fact(self, ctx: commands.Context) -> None: """Shows a random fact about Valentine's Day.""" embed = discord.Embed( title=choice(FACTS['titles']), @@ -47,7 +47,7 @@ class ValentineFacts(commands.Cog): await ctx.channel.send(embed=embed) -def setup(bot): +def setup(bot: commands.Bot) -> None: """Who is Valentine Cog load.""" bot.add_cog(ValentineFacts(bot)) log.info("ValentineFacts cog loaded") diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 3249a9cf..8732eb22 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -29,7 +29,7 @@ async def disambiguate( choices = (f'{index}: {entry}' for index, entry in enumerate(entries, start=1)) - def check(message): + def check(message: discord.Message) -> bool: return (message.content.isdigit() and message.author == ctx.author and message.channel == ctx.channel) @@ -109,7 +109,7 @@ def replace_many( pattern = "|".join(re.escape(word) for word in words_to_replace) regex = re.compile(pattern, re.I if ignore_case else 0) - def _repl(match): + def _repl(match: re.Match) -> str: """Returns replacement depending on `ignore_case` and `match_case`""" word = match.group(0) replacement = replacements[word.lower() if ignore_case else word] diff --git a/bot/utils/halloween/spookifications.py b/bot/utils/halloween/spookifications.py index 69b49919..11f69850 100644 --- a/bot/utils/halloween/spookifications.py +++ b/bot/utils/halloween/spookifications.py @@ -7,7 +7,7 @@ from PIL import ImageOps log = logging.getLogger() -def inversion(im): +def inversion(im: Image) -> Image: """ Inverts the image. @@ -18,7 +18,7 @@ def inversion(im): return inv -def pentagram(im): +def pentagram(im: Image) -> Image: """Adds pentagram to the image.""" im = im.convert('RGB') wt, ht = im.size @@ -28,7 +28,7 @@ def pentagram(im): return im -def bat(im): +def bat(im: Image) -> Image: """ Adds a bat silhoutte to the image. @@ -50,7 +50,7 @@ def bat(im): return im -def get_random_effect(im): +def get_random_effect(im: Image) -> Image: """Randomly selects and applies an effect.""" effects = [inversion, pentagram, bat] effect = choice(effects) -- cgit v1.2.3 From da20c52802cbb4c68cfbb058e9ffc986b591240f Mon Sep 17 00:00:00 2001 From: "S. Co1" Date: Wed, 11 Sep 2019 11:33:16 -0400 Subject: Fix incorrect merge conflict resolutions, lint remaining items --- bot/seasons/easter/avatar_easterifier.py | 2 +- bot/seasons/easter/bunny_name_generator.py | 6 +++--- bot/seasons/easter/easter_riddle.py | 2 +- bot/seasons/easter/egghead_quiz.py | 4 ++-- bot/seasons/easter/traditions.py | 2 +- bot/seasons/evergreen/minesweeper.py | 8 ++++---- bot/seasons/evergreen/showprojects.py | 4 ++-- bot/seasons/evergreen/snakes/utils.py | 26 ++++++++++++++++---------- bot/seasons/evergreen/speedrun.py | 2 +- bot/seasons/halloween/hacktoberstats.py | 2 +- bot/utils/__init__.py | 2 +- tox.ini | 2 +- 12 files changed, 34 insertions(+), 28 deletions(-) (limited to 'bot/utils/__init__.py') diff --git a/bot/seasons/easter/avatar_easterifier.py b/bot/seasons/easter/avatar_easterifier.py index f056068e..85c32909 100644 --- a/bot/seasons/easter/avatar_easterifier.py +++ b/bot/seasons/easter/avatar_easterifier.py @@ -34,7 +34,7 @@ class AvatarEasterifier(commands.Cog): r1, g1, b1 = x def distance(point: Tuple[int, int, int]) -> Tuple[int, int, int]: - """Finds the difference between a pastel colour and the original pixel colour""" + """Finds the difference between a pastel colour and the original pixel colour.""" r2, g2, b2 = point return ((r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2) diff --git a/bot/seasons/easter/bunny_name_generator.py b/bot/seasons/easter/bunny_name_generator.py index 22957b7f..97c467e1 100644 --- a/bot/seasons/easter/bunny_name_generator.py +++ b/bot/seasons/easter/bunny_name_generator.py @@ -47,7 +47,7 @@ class BunnyNameGenerator(commands.Cog): return new_name def append_name(self, displayname: str) -> str: - """Adds a suffix to the end of the Discord name""" + """Adds a suffix to the end of the Discord name.""" extensions = ['foot', 'ear', 'nose', 'tail'] suffix = random.choice(extensions) appended_name = displayname + suffix @@ -56,12 +56,12 @@ class BunnyNameGenerator(commands.Cog): @commands.command() async def bunnyname(self, ctx: commands.Context) -> None: - """Picks a random bunny name from a JSON file""" + """Picks a random bunny name from a JSON file.""" await ctx.send(random.choice(BUNNY_NAMES["names"])) @commands.command() async def bunnifyme(self, ctx: commands.Context) -> None: - """Gets your Discord username and bunnifies it""" + """Gets your Discord username and bunnifies it.""" username = ctx.message.author.display_name # If name contains spaces or other separators, get the individual words to randomly bunnify diff --git a/bot/seasons/easter/easter_riddle.py b/bot/seasons/easter/easter_riddle.py index c3f19055..4b98b204 100644 --- a/bot/seasons/easter/easter_riddle.py +++ b/bot/seasons/easter/easter_riddle.py @@ -84,7 +84,7 @@ class EasterRiddle(commands.Cog): @commands.Cog.listener() async def on_message(self, message: discord.Messaged) -> None: - """If a non-bot user enters a correct answer, their username gets added to self.winners""" + """If a non-bot user enters a correct answer, their username gets added to self.winners.""" if self.current_channel != message.channel: return diff --git a/bot/seasons/easter/egghead_quiz.py b/bot/seasons/easter/egghead_quiz.py index 0b175bf1..bd179fe2 100644 --- a/bot/seasons/easter/egghead_quiz.py +++ b/bot/seasons/easter/egghead_quiz.py @@ -97,13 +97,13 @@ class EggheadQuiz(commands.Cog): @staticmethod async def already_reacted(message: discord.Message, user: Union[discord.Member, discord.User]) -> bool: - """Returns whether a given user has reacted more than once to a given message""" + """Returns whether a given user has reacted more than once to a given message.""" users = [u.id for reaction in [await r.users().flatten() for r in message.reactions] for u in reaction] return users.count(user.id) > 1 # Old reaction plus new reaction @commands.Cog.listener() async def on_reaction_add(self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]) -> None: - """Listener to listen specifically for reactions of quiz messages""" + """Listener to listen specifically for reactions of quiz messages.""" if user.bot: return if reaction.message.id not in self.quiz_messages: diff --git a/bot/seasons/easter/traditions.py b/bot/seasons/easter/traditions.py index 4fb4694f..9529823f 100644 --- a/bot/seasons/easter/traditions.py +++ b/bot/seasons/easter/traditions.py @@ -19,7 +19,7 @@ class Traditions(commands.Cog): @commands.command(aliases=('eastercustoms',)) async def easter_tradition(self, ctx: commands.Context) -> None: - """Responds with a random tradition or custom""" + """Responds with a random tradition or custom.""" random_country = random.choice(list(traditions)) await ctx.send(f"{random_country}:\n{traditions[random_country]}") diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 015b09df..b0ba8145 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -33,7 +33,7 @@ class CoordinateConverter(commands.Converter): """Converter for Coordinates.""" async def convert(self, ctx: commands.Context, coordinate: str) -> typing.Tuple[int, int]: - """Take in a coordinate string and turn it into x, y""" + """Take in a coordinate string and turn it into an (x, y) tuple.""" if not 2 <= len(coordinate) <= 3: raise commands.BadArgument('Invalid co-ordinate provided') @@ -81,7 +81,7 @@ class Minesweeper(commands.Cog): @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True) async def minesweeper_group(self, ctx: commands.Context) -> None: - """Commands for Playing Minesweeper""" + """Commands for Playing Minesweeper.""" await ctx.send_help(ctx.command) @staticmethod @@ -216,7 +216,7 @@ class Minesweeper(commands.Cog): self.reveal_zeros(revealed, board, x_, y_) async def check_if_won(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard) -> bool: - """Checks if a player has won""" + """Checks if a player has won.""" if any( revealed[y][x] in ["hidden", "flag"] and board[y][x] != "bomb" for x in range(10) @@ -268,7 +268,7 @@ class Minesweeper(commands.Cog): @minesweeper_group.command(name="end") async def end_command(self, ctx: commands.Context) -> None: - """End your current game""" + """End your current game.""" game = self.games[ctx.author.id] game.revealed = game.board await self.update_boards(ctx) diff --git a/bot/seasons/evergreen/showprojects.py b/bot/seasons/evergreen/showprojects.py index d41132aa..a943e548 100644 --- a/bot/seasons/evergreen/showprojects.py +++ b/bot/seasons/evergreen/showprojects.py @@ -17,7 +17,7 @@ class ShowProjects(commands.Cog): @commands.Cog.listener() async def on_message(self, message: Message) -> None: - """Adds reactions to posts in #show-your-projects""" + """Adds reactions to posts in #show-your-projects.""" reactions = ["\U0001f44d", "\U00002764", "\U0001f440", "\U0001f389", "\U0001f680", "\U00002b50", "\U0001f6a9"] if (message.channel.id == Channels.show_your_projects and message.author.bot is False @@ -29,6 +29,6 @@ class ShowProjects(commands.Cog): def setup(bot: commands.Bot) -> None: - """Show Projects Reaction Cog""" + """Show Projects Reaction Cog.""" bot.add_cog(ShowProjects(bot)) log.info("ShowProjects cog loaded") diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index 76809bd4..7d6caf04 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -388,8 +388,7 @@ class SnakeAndLaddersGame: """ Create a new Snakes and Ladders game. - Listen for reactions until players have joined, - and the game has been started. + Listen for reactions until players have joined, and the game has been started. """ def startup_event_check(reaction_: Reaction, user_: Member) -> bool: """Make sure that this reaction is what we want to operate on.""" @@ -457,6 +456,7 @@ class SnakeAndLaddersGame: return # We're done, no reactions for the last 5 minutes async def _add_player(self, user: Member) -> None: + """Add player to game.""" self.players.append(user) self.player_tiles[user.id] = 1 @@ -490,7 +490,7 @@ class SnakeAndLaddersGame: delete_after=10 ) - async def player_leave(self, user: Member) -> None: + async def player_leave(self, user: Member) -> bool: """ Handle players leaving the game. @@ -515,11 +515,13 @@ class SnakeAndLaddersGame: is_surrendered = True self._destruct() - async def cancel_game(self, user: Member) -> None: - """Allow the game author to cancel the running game.""" - if not user == self.author: - await self.channel.send(user.mention + " Only the author of the game can cancel it.", delete_after=10) - return + return is_surrendered + else: + await self.channel.send(user.mention + " You are not in the match.", delete_after=10) + return is_surrendered + + async def cancel_game(self) -> None: + """Cancel the running game.""" await self.channel.send("**Snakes and Ladders**: Game has been canceled.") self._destruct() @@ -670,6 +672,7 @@ class SnakeAndLaddersGame: self.round_has_rolled[user.id] = True async def _complete_round(self) -> None: + """At the conclusion of a round check to see if there's been a winner.""" self.state = 'post_round' # check for winner @@ -684,19 +687,22 @@ class SnakeAndLaddersGame: self._destruct() def _check_winner(self) -> Member: + """Return a winning member if we're in the post-round state and there's a winner.""" if self.state != 'post_round': return None return next((player for player in self.players if self.player_tiles[player.id] == 100), None) def _check_all_rolled(self) -> bool: + """Check if all members have made their roll.""" return all(rolled for rolled in self.round_has_rolled.values()) def _destruct(self) -> None: + """Clean up the finished game object.""" del self.snakes.active_sal[self.channel] - def _board_coordinate_from_index(self, index: int) -> Tuple[float, float]: - # converts the tile number to the x/y coordinates for graphical purposes + def _board_coordinate_from_index(self, index: int) -> Tuple[int, int]: + """Convert the tile number to the x/y coordinates for graphical purposes.""" y_level = 9 - math.floor((index - 1) / 10) is_reversed = math.floor((index - 1) / 10) % 2 != 0 x_level = (index - 1) % 10 diff --git a/bot/seasons/evergreen/speedrun.py b/bot/seasons/evergreen/speedrun.py index 2f59c886..76c5e8d3 100644 --- a/bot/seasons/evergreen/speedrun.py +++ b/bot/seasons/evergreen/speedrun.py @@ -23,6 +23,6 @@ class Speedrun(commands.Cog): def setup(bot: commands.Bot) -> None: - """Load the Speedrun cog""" + """Load the Speedrun cog.""" bot.add_cog(Speedrun(bot)) log.info("Speedrun cog loaded") diff --git a/bot/seasons/halloween/hacktoberstats.py b/bot/seasons/halloween/hacktoberstats.py index 5687a5c7..0f513953 100644 --- a/bot/seasons/halloween/hacktoberstats.py +++ b/bot/seasons/halloween/hacktoberstats.py @@ -19,7 +19,7 @@ PRS_FOR_SHIRT = 4 # Minimum number of PRs before a shirt is awarded class HacktoberStats(commands.Cog): """Hacktoberfest statistics Cog.""" - def __init__(self, bot): + def __init__(self, bot: commands.Bot): self.bot = bot self.link_json = Path("bot/resources/github_links.json") self.linked_accounts = self.load_linked_users() diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py index 8732eb22..0aa50af6 100644 --- a/bot/utils/__init__.py +++ b/bot/utils/__init__.py @@ -110,7 +110,7 @@ def replace_many( regex = re.compile(pattern, re.I if ignore_case else 0) def _repl(match: re.Match) -> str: - """Returns replacement depending on `ignore_case` and `match_case`""" + """Returns replacement depending on `ignore_case` and `match_case`.""" word = match.group(0) replacement = replacements[word.lower() if ignore_case else word] diff --git a/tox.ini b/tox.ini index dec88854..ee898b0d 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ ignore= # Docstring Quotes D301,D302, # Docstring Content - D400,D401,D402,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414 + D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417 # Type Annotations TYP002,TYP003,TYP101,TYP102,TYP204,TYP206 exclude= -- cgit v1.2.3