From 1447327e337e0565a25ff83476d285c8fe4b1e72 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 21 Apr 2020 19:29:11 +0300 Subject: Improve `!pep` command - Made `pep_number` type hint to `int` to avoid unnecessary manual converting. - Added `ctx.trigger_typing` calling to show user that bot is responding. --- bot/cogs/utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 3ed471bbf..bf8887538 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -53,13 +53,10 @@ class Utils(Cog): 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) -> None: + async def pep_command(self, ctx: Context, pep_number: int) -> None: """Fetches information about a PEP and sends it to the channel.""" - if pep_number.isdigit(): - pep_number = int(pep_number) - else: - await ctx.invoke(self.bot.get_command("help"), "pep") - return + # Trigger typing in chat to show users that bot is responding + await ctx.trigger_typing() # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. if pep_number == 0: -- cgit v1.2.3 From 6b6d2a75f3cb6d31c1ed287362c28ca47298b019 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 25 Apr 2020 08:26:14 +0300 Subject: Moved `async_cache` decorator from `Doc` cog file to `utils/cache.py` --- bot/cogs/doc.py | 32 ++------------------------------ bot/utils/cache.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 30 deletions(-) create mode 100644 bot/utils/cache.py diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index 204cffb37..ff60fc80a 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -6,7 +6,7 @@ import textwrap from collections import OrderedDict from contextlib import suppress from types import SimpleNamespace -from typing import Any, Callable, Optional, Tuple +from typing import Optional, Tuple import discord from bs4 import BeautifulSoup @@ -23,6 +23,7 @@ from bot.constants import MODERATION_ROLES, RedirectOutput from bot.converters import ValidPythonIdentifier, ValidURL from bot.decorators import with_role from bot.pagination import LinePaginator +from bot.utils.cache import async_cache log = logging.getLogger(__name__) @@ -66,35 +67,6 @@ FAILED_REQUEST_RETRY_AMOUNT = 3 NOT_FOUND_DELETE_DELAY = RedirectOutput.delete_delay -def async_cache(max_size: int = 128, arg_offset: int = 0) -> Callable: - """ - LRU cache implementation for coroutines. - - 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: Callable) -> Callable: - """Define the async_cache decorator.""" - @functools.wraps(function) - async def wrapper(*args) -> Any: - """Decorator wrapper for the caching logic.""" - key = ':'.join(args[arg_offset:]) - - value = async_cache.cache.get(key) - if value is None: - if len(async_cache.cache) > max_size: - async_cache.cache.popitem(last=False) - - async_cache.cache[key] = await function(*args) - return async_cache.cache[key] - return wrapper - return decorator - - class DocMarkdownConverter(MarkdownConverter): """Subclass markdownify's MarkdownCoverter to provide custom conversion methods.""" diff --git a/bot/utils/cache.py b/bot/utils/cache.py new file mode 100644 index 000000000..338924df8 --- /dev/null +++ b/bot/utils/cache.py @@ -0,0 +1,32 @@ +import functools +from collections import OrderedDict +from typing import Any, Callable + + +def async_cache(max_size: int = 128, arg_offset: int = 0) -> Callable: + """ + LRU cache implementation for coroutines. + + 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: Callable) -> Callable: + """Define the async_cache decorator.""" + @functools.wraps(function) + async def wrapper(*args) -> Any: + """Decorator wrapper for the caching logic.""" + key = ':'.join(str(args[arg_offset:])) + + value = async_cache.cache.get(key) + if value is None: + if len(async_cache.cache) > max_size: + async_cache.cache.popitem(last=False) + + async_cache.cache[key] = await function(*args) + return async_cache.cache[key] + return wrapper + return decorator -- cgit v1.2.3 From bf26ad7f7648384182d95d76618faf1c9392b403 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 25 Apr 2020 08:45:35 +0300 Subject: Created new task in `Utils` cog: `refresh_peps_urls` Task refresh listing of PEPs + URLs in every 24 hours --- bot/cogs/utils.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index bf8887538..8e7f41088 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -5,11 +5,12 @@ import unicodedata from asyncio import TimeoutError, sleep from email.parser import HeaderParser from io import StringIO -from typing import Tuple, Union +from typing import Dict, Tuple, Union from dateutil import relativedelta from discord import Colour, Embed, Message, Role from discord.ext.commands import BadArgument, Cog, Context, command +from discord.ext.tasks import loop from bot.bot import Bot from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES @@ -51,6 +52,24 @@ class Utils(Cog): self.base_pep_url = "http://www.python.org/dev/peps/pep-" self.base_github_pep_url = "https://raw.githubusercontent.com/python/peps/master/pep-" + self.peps_listing_api_url = "https://api.github.com/repos/python/peps/contents?ref=master" + + self.peps: Dict[int, str] = {} + self.refresh_peps_urls.start() + + @loop(hours=24) + async def refresh_peps_urls(self) -> None: + """Refresh PEP URLs listing every day at once.""" + # Wait until HTTP client is available + await self.bot.wait_until_guild_available() + + async with self.bot.http_session.get(self.peps_listing_api_url) as resp: + listing = await resp.json() + + for file in listing: + name = file["name"] + if name.startswith("pep-") and (name.endswith(".txt") or name.endswith(".rst")): + self.peps[int(name.split(".")[0].split("-")[1])] = file["download_url"] @command(name='pep', aliases=('get_pep', 'p')) async def pep_command(self, ctx: Context, pep_number: int) -> None: -- cgit v1.2.3 From a2f0de1c34dc320f4ee61d64a33b0d866bf41af2 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 25 Apr 2020 09:22:57 +0300 Subject: Refactor `pep` command, implement caching Moved PEP embed getting to function, that use caching. --- bot/cogs/utils.py | 101 +++++++++++++++++++++++++----------------------------- 1 file changed, 46 insertions(+), 55 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 8e7f41088..995221b80 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -15,6 +15,7 @@ from discord.ext.tasks import loop from bot.bot import Bot from bot.constants import Channels, MODERATION_ROLES, Mention, STAFF_ROLES from bot.decorators import in_channel, with_role +from bot.utils.cache import async_cache from bot.utils.time import humanize_delta log = logging.getLogger(__name__) @@ -79,59 +80,10 @@ class Utils(Cog): # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. if pep_number == 0: - return await self.send_pep_zero(ctx) - - possible_extensions = ['.txt', '.rst'] - found_pep = False - for extension in possible_extensions: - # Attempt to fetch the PEP - pep_url = f"{self.base_github_pep_url}{pep_number:04}{extension}" - log.trace(f"Requesting PEP {pep_number} with {pep_url}") - response = await self.bot.http_session.get(pep_url) - - if response.status == 200: - log.trace("PEP found") - found_pep = True - - pep_content = await response.text() - - # Taken from https://github.com/python/peps/blob/master/pep0/pep.py#L179 - pep_header = HeaderParser().parse(StringIO(pep_content)) - - # Assemble the embed - pep_embed = Embed( - title=f"**PEP {pep_number} - {pep_header['Title']}**", - description=f"[Link]({self.base_pep_url}{pep_number:04})", - ) - - pep_embed.set_thumbnail(url=ICON_URL) - - # Add the interesting information - fields_to_check = ("Status", "Python-Version", "Created", "Type") - for field in fields_to_check: - # Check for a PEP metadata field that is present but has an empty value - # embed field values can't contain an empty string - if pep_header.get(field, ""): - pep_embed.add_field(name=field, value=pep_header[field]) - - elif response.status != 404: - # any response except 200 and 404 is expected - found_pep = True # actually not, but it's easier to display this way - log.trace(f"The user requested PEP {pep_number}, but the response had an unexpected status code: " - f"{response.status}.\n{response.text}") - - error_message = "Unexpected HTTP error during PEP search. Please let us know." - pep_embed = Embed(title="Unexpected error", description=error_message) - pep_embed.colour = Colour.red() - break - - if not found_pep: - log.trace("PEP was not found") - not_found = f"PEP {pep_number} does not exist." - pep_embed = Embed(title="PEP not found", description=not_found) - pep_embed.colour = Colour.red() - - await ctx.message.channel.send(embed=pep_embed) + pep_embed = await self.get_pep_zero_embed() + else: + pep_embed = await self.get_pep_embed(pep_number) + await ctx.send(embed=pep_embed) @command() @in_channel(Channels.bot_commands, bypass_roles=STAFF_ROLES) @@ -310,7 +262,7 @@ class Utils(Cog): for reaction in options: await message.add_reaction(reaction) - async def send_pep_zero(self, ctx: Context) -> None: + async def get_pep_zero_embed(self) -> Embed: """Send information about PEP 0.""" pep_embed = Embed( title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", @@ -321,7 +273,46 @@ class Utils(Cog): pep_embed.add_field(name="Created", value="13-Jul-2000") pep_embed.add_field(name="Type", value="Informational") - await ctx.send(embed=pep_embed) + return pep_embed + + @async_cache(arg_offset=1) + async def get_pep_embed(self, pep_nr: int) -> Embed: + """Fetch, generate and return PEP embed. Implement `async_cache`.""" + if pep_nr not in self.peps: + log.trace(f"PEP {pep_nr} was not found") + not_found = f"PEP {pep_nr} does not exist." + return Embed(title="PEP not found", description=not_found, colour=Colour.red()) + response = await self.bot.http_session.get(self.peps[pep_nr]) + + if response.status == 200: + log.trace(f"PEP {pep_nr} found") + pep_content = await response.text() + + # Taken from https://github.com/python/peps/blob/master/pep0/pep.py#L179 + pep_header = HeaderParser().parse(StringIO(pep_content)) + + # Assemble the embed + pep_embed = Embed( + title=f"**PEP {pep_nr} - {pep_header['Title']}**", + description=f"[Link]({self.base_pep_url}{pep_nr:04})", + ) + + pep_embed.set_thumbnail(url=ICON_URL) + + # Add the interesting information + fields_to_check = ("Status", "Python-Version", "Created", "Type") + for field in fields_to_check: + # Check for a PEP metadata field that is present but has an empty value + # embed field values can't contain an empty string + if pep_header.get(field, ""): + pep_embed.add_field(name=field, value=pep_header[field]) + return pep_embed + else: + log.trace(f"The user requested PEP {pep_nr}, but the response had an unexpected status code: " + f"{response.status}.\n{response.text}") + + error_message = "Unexpected HTTP error during PEP search. Please let us know." + return Embed(title="Unexpected error", description=error_message, colour=Colour.red()) def setup(bot: Bot) -> None: -- cgit v1.2.3 From fa3d369b68644dfa30d1db22ca7dd1c76b9d608e Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 25 Apr 2020 09:24:27 +0300 Subject: Replaced 24 hours with 3 hours in `refresh_peps_urls` Made modification to include new PEPs faster. --- bot/cogs/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 995221b80..626169b42 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -58,9 +58,9 @@ class Utils(Cog): self.peps: Dict[int, str] = {} self.refresh_peps_urls.start() - @loop(hours=24) + @loop(hours=3) async def refresh_peps_urls(self) -> None: - """Refresh PEP URLs listing every day at once.""" + """Refresh PEP URLs listing in every 3 hours.""" # Wait until HTTP client is available await self.bot.wait_until_guild_available() -- cgit v1.2.3 From 2b8efb61c766cc1982e022608d3098c8cca6783b Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 13 May 2020 19:54:11 +0300 Subject: PEP Improvisations: Moved PEP functions to one region --- bot/cogs/utils.py | 56 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 626169b42..7c6541ccb 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -58,33 +58,6 @@ class Utils(Cog): self.peps: Dict[int, str] = {} self.refresh_peps_urls.start() - @loop(hours=3) - async def refresh_peps_urls(self) -> None: - """Refresh PEP URLs listing in every 3 hours.""" - # Wait until HTTP client is available - await self.bot.wait_until_guild_available() - - async with self.bot.http_session.get(self.peps_listing_api_url) as resp: - listing = await resp.json() - - for file in listing: - name = file["name"] - if name.startswith("pep-") and (name.endswith(".txt") or name.endswith(".rst")): - self.peps[int(name.split(".")[0].split("-")[1])] = file["download_url"] - - @command(name='pep', aliases=('get_pep', 'p')) - async def pep_command(self, ctx: Context, pep_number: int) -> None: - """Fetches information about a PEP and sends it to the channel.""" - # Trigger typing in chat to show users that bot is responding - await ctx.trigger_typing() - - # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. - if pep_number == 0: - pep_embed = await self.get_pep_zero_embed() - else: - pep_embed = await self.get_pep_embed(pep_number) - await ctx.send(embed=pep_embed) - @command() @in_channel(Channels.bot_commands, bypass_roles=STAFF_ROLES) async def charinfo(self, ctx: Context, *, characters: str) -> None: @@ -262,6 +235,35 @@ class Utils(Cog): for reaction in options: await message.add_reaction(reaction) + # PEPs area + + @loop(hours=3) + async def refresh_peps_urls(self) -> None: + """Refresh PEP URLs listing in every 3 hours.""" + # Wait until HTTP client is available + await self.bot.wait_until_guild_available() + + async with self.bot.http_session.get(self.peps_listing_api_url) as resp: + listing = await resp.json() + + for file in listing: + name = file["name"] + if name.startswith("pep-") and (name.endswith(".txt") or name.endswith(".rst")): + self.peps[int(name.split(".")[0].split("-")[1])] = file["download_url"] + + @command(name='pep', aliases=('get_pep', 'p')) + async def pep_command(self, ctx: Context, pep_number: int) -> None: + """Fetches information about a PEP and sends it to the channel.""" + # Trigger typing in chat to show users that bot is responding + await ctx.trigger_typing() + + # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. + if pep_number == 0: + pep_embed = await self.get_pep_zero_embed() + else: + pep_embed = await self.get_pep_embed(pep_number) + await ctx.send(embed=pep_embed) + async def get_pep_zero_embed(self) -> Embed: """Send information about PEP 0.""" pep_embed = Embed( -- cgit v1.2.3 From 34509a59664fc7d00e1eff85800d1e35e33ccb85 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 13 May 2020 19:56:27 +0300 Subject: PEP Improvisations: Added `staticmethod` decorator to `get_pep_zero_embed` --- bot/cogs/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 7c6541ccb..e56ffb4dd 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -264,7 +264,8 @@ class Utils(Cog): pep_embed = await self.get_pep_embed(pep_number) await ctx.send(embed=pep_embed) - async def get_pep_zero_embed(self) -> Embed: + @staticmethod + async def get_pep_zero_embed() -> Embed: """Send information about PEP 0.""" pep_embed = Embed( title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", -- cgit v1.2.3 From 34b8ae45e644226c75ce070db4b8b375129d278a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 13 May 2020 19:59:03 +0300 Subject: PEP Improvisations: Replaced `wait_until_guild_available` with `wait_until_ready` --- bot/cogs/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index e56ffb4dd..91f462c42 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -241,7 +241,7 @@ class Utils(Cog): async def refresh_peps_urls(self) -> None: """Refresh PEP URLs listing in every 3 hours.""" # Wait until HTTP client is available - await self.bot.wait_until_guild_available() + await self.bot.wait_until_ready() async with self.bot.http_session.get(self.peps_listing_api_url) as resp: listing = await resp.json() -- cgit v1.2.3 From 04cdb55fabfc21fc548754ed10aafec845ffe8db Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 13 May 2020 20:02:25 +0300 Subject: PEP Improvisations: Added logging to PEP URLs fetching task --- bot/cogs/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 91f462c42..b72ba8d5a 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -242,14 +242,17 @@ class Utils(Cog): """Refresh PEP URLs listing in every 3 hours.""" # Wait until HTTP client is available await self.bot.wait_until_ready() + log.trace("Started refreshing PEP URLs.") async with self.bot.http_session.get(self.peps_listing_api_url) as resp: listing = await resp.json() + log.trace("Got PEP URLs listing from GitHub API") for file in listing: name = file["name"] if name.startswith("pep-") and (name.endswith(".txt") or name.endswith(".rst")): self.peps[int(name.split(".")[0].split("-")[1])] = file["download_url"] + log.info("Successfully refreshed PEP URLs listing.") @command(name='pep', aliases=('get_pep', 'p')) async def pep_command(self, ctx: Context, pep_number: int) -> None: -- cgit v1.2.3 From b6968695a9da0f3c3597b9fb187753b98b778718 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Wed, 13 May 2020 20:08:35 +0300 Subject: PEP Improvisations: Made PEP URLs refreshing task PEP number resolving easier --- bot/cogs/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index b72ba8d5a..f6b56db73 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -250,8 +250,10 @@ class Utils(Cog): for file in listing: name = file["name"] - if name.startswith("pep-") and (name.endswith(".txt") or name.endswith(".rst")): - self.peps[int(name.split(".")[0].split("-")[1])] = file["download_url"] + name: str + if name.startswith("pep-") and name.endswith((".rst", ".txt")): + pep_number = name.replace("pep-", "").split(".")[0] + self.peps[int(pep_number)] = file["download_url"] log.info("Successfully refreshed PEP URLs listing.") @command(name='pep', aliases=('get_pep', 'p')) -- cgit v1.2.3 From be71ac7847723f8f90dc095ebaa7257e189fa1c6 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 14 May 2020 08:42:55 +0300 Subject: PEP Improvisations: Implemented stats to PEP command --- bot/cogs/utils.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index f6b56db73..bb655085d 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -265,12 +265,16 @@ class Utils(Cog): # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. if pep_number == 0: pep_embed = await self.get_pep_zero_embed() + success = True else: - pep_embed = await self.get_pep_embed(pep_number) + pep_embed, success = await self.get_pep_embed(pep_number) await ctx.send(embed=pep_embed) - @staticmethod - async def get_pep_zero_embed() -> Embed: + if success: + log.trace(f"PEP {pep_number} getting and sending finished successfully. Increasing stat.") + self.bot.stats.incr(f"pep_fetches.{pep_number}") + + async def get_pep_zero_embed(self) -> Embed: """Send information about PEP 0.""" pep_embed = Embed( title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", @@ -284,12 +288,12 @@ class Utils(Cog): return pep_embed @async_cache(arg_offset=1) - async def get_pep_embed(self, pep_nr: int) -> Embed: + async def get_pep_embed(self, pep_nr: int) -> Tuple[Embed, bool]: """Fetch, generate and return PEP embed. Implement `async_cache`.""" if pep_nr not in self.peps: log.trace(f"PEP {pep_nr} was not found") not_found = f"PEP {pep_nr} does not exist." - return Embed(title="PEP not found", description=not_found, colour=Colour.red()) + return Embed(title="PEP not found", description=not_found, colour=Colour.red()), False response = await self.bot.http_session.get(self.peps[pep_nr]) if response.status == 200: @@ -314,13 +318,13 @@ class Utils(Cog): # embed field values can't contain an empty string if pep_header.get(field, ""): pep_embed.add_field(name=field, value=pep_header[field]) - return pep_embed + return pep_embed, True else: log.trace(f"The user requested PEP {pep_nr}, but the response had an unexpected status code: " f"{response.status}.\n{response.text}") error_message = "Unexpected HTTP error during PEP search. Please let us know." - return Embed(title="Unexpected error", description=error_message, colour=Colour.red()) + return Embed(title="Unexpected error", description=error_message, colour=Colour.red()), False def setup(bot: Bot) -> None: -- cgit v1.2.3 From d560b8315f46b7598c0ef7b7b5c75b3c035796da Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 14 May 2020 08:45:16 +0300 Subject: PEP Improvisations: Moved `get_pep_zero_embed` to outside of Cog --- bot/cogs/utils.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index bb655085d..15a3e9e8c 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -45,6 +45,20 @@ Namespaces are one honking great idea -- let's do more of those! ICON_URL = "https://www.python.org/static/opengraph-icon-200x200.png" +def get_pep_zero_embed() -> Embed: + """Send information about PEP 0.""" + pep_embed = Embed( + title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", + description=f"[Link](https://www.python.org/dev/peps/)" + ) + pep_embed.set_thumbnail(url=ICON_URL) + pep_embed.add_field(name="Status", value="Active") + pep_embed.add_field(name="Created", value="13-Jul-2000") + pep_embed.add_field(name="Type", value="Informational") + + return pep_embed + + class Utils(Cog): """A selection of utilities which don't have a clear category.""" @@ -264,7 +278,7 @@ class Utils(Cog): # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. if pep_number == 0: - pep_embed = await self.get_pep_zero_embed() + pep_embed = get_pep_zero_embed() success = True else: pep_embed, success = await self.get_pep_embed(pep_number) @@ -274,19 +288,6 @@ class Utils(Cog): log.trace(f"PEP {pep_number} getting and sending finished successfully. Increasing stat.") self.bot.stats.incr(f"pep_fetches.{pep_number}") - async def get_pep_zero_embed(self) -> Embed: - """Send information about PEP 0.""" - pep_embed = Embed( - title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", - description=f"[Link](https://www.python.org/dev/peps/)" - ) - pep_embed.set_thumbnail(url=ICON_URL) - pep_embed.add_field(name="Status", value="Active") - pep_embed.add_field(name="Created", value="13-Jul-2000") - pep_embed.add_field(name="Type", value="Informational") - - return pep_embed - @async_cache(arg_offset=1) async def get_pep_embed(self, pep_nr: int) -> Tuple[Embed, bool]: """Fetch, generate and return PEP embed. Implement `async_cache`.""" -- cgit v1.2.3 From e2c30322fc32b601faa0b2a66367cd98f91fe627 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 14 May 2020 09:07:03 +0300 Subject: PEP Improvisations: Fix imports Replace `in_channel` with `in_whitelist`. This mistake was made to merge conflicts. --- bot/cogs/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 0b1436f3a..fe7e5b3e9 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -12,7 +12,7 @@ from discord.ext.tasks import loop from bot.bot import Bot from bot.constants import Channels, MODERATION_ROLES, STAFF_ROLES -from bot.decorators import in_channel, with_role +from bot.decorators import in_whitelist, with_role from bot.utils.cache import async_cache log = logging.getLogger(__name__) -- cgit v1.2.3 From 36dac33d81bd174d8a005e6fc02055d8d096cfd8 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 15 May 2020 09:15:51 +0300 Subject: PEP Improvisations: Remove unnecessary typehint Removed unnecessary type hint that I used for IDE and what I forget to remove. --- bot/cogs/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index fe7e5b3e9..c24252aa6 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -220,7 +220,6 @@ class Utils(Cog): for file in listing: name = file["name"] - name: str if name.startswith("pep-") and name.endswith((".rst", ".txt")): pep_number = name.replace("pep-", "").split(".")[0] self.peps[int(pep_number)] = file["download_url"] -- cgit v1.2.3 From 248ce24936cd09e560c651c6c5953d1ea90d8229 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 15 May 2020 09:17:20 +0300 Subject: PEP Improvisations: Fix log text formatting Use repo own alignment of multiline text. --- bot/cogs/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index c24252aa6..6562ea0b4 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -276,8 +276,10 @@ class Utils(Cog): pep_embed.add_field(name=field, value=pep_header[field]) return pep_embed, True else: - log.trace(f"The user requested PEP {pep_nr}, but the response had an unexpected status code: " - f"{response.status}.\n{response.text}") + log.trace( + f"The user requested PEP {pep_nr}, but the response had an unexpected status code: " + f"{response.status}.\n{response.text}" + ) error_message = "Unexpected HTTP error during PEP search. Please let us know." return Embed(title="Unexpected error", description=error_message, colour=Colour.red()), False -- cgit v1.2.3 From 378929eff99fa6330c2d1a5b1c1108ff80e11d92 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 15 May 2020 09:19:22 +0300 Subject: PEP Improvisations: Move `get_pep_zero_embed` back to Cog Moved `get_pep_zero_embed` back to the cog, but made this `staticmethod`. --- bot/cogs/utils.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 6562ea0b4..80cdd9210 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -42,20 +42,6 @@ Namespaces are one honking great idea -- let's do more of those! ICON_URL = "https://www.python.org/static/opengraph-icon-200x200.png" -def get_pep_zero_embed() -> Embed: - """Send information about PEP 0.""" - pep_embed = Embed( - title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", - description=f"[Link](https://www.python.org/dev/peps/)" - ) - pep_embed.set_thumbnail(url=ICON_URL) - pep_embed.add_field(name="Status", value="Active") - pep_embed.add_field(name="Created", value="13-Jul-2000") - pep_embed.add_field(name="Type", value="Informational") - - return pep_embed - - class Utils(Cog): """A selection of utilities which don't have a clear category.""" @@ -233,7 +219,7 @@ class Utils(Cog): # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. if pep_number == 0: - pep_embed = get_pep_zero_embed() + pep_embed = self.get_pep_zero_embed() success = True else: pep_embed, success = await self.get_pep_embed(pep_number) @@ -243,6 +229,20 @@ class Utils(Cog): log.trace(f"PEP {pep_number} getting and sending finished successfully. Increasing stat.") self.bot.stats.incr(f"pep_fetches.{pep_number}") + @staticmethod + def get_pep_zero_embed() -> Embed: + """Send information about PEP 0.""" + pep_embed = Embed( + title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", + description=f"[Link](https://www.python.org/dev/peps/)" + ) + pep_embed.set_thumbnail(url=ICON_URL) + pep_embed.add_field(name="Status", value="Active") + pep_embed.add_field(name="Created", value="13-Jul-2000") + pep_embed.add_field(name="Type", value="Informational") + + return pep_embed + @async_cache(arg_offset=1) async def get_pep_embed(self, pep_nr: int) -> Tuple[Embed, bool]: """Fetch, generate and return PEP embed. Implement `async_cache`.""" -- cgit v1.2.3 From c412ceb33b1309a728d1e607e0e97b5ea6f1be3d Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 15 May 2020 09:20:05 +0300 Subject: PEP Improvisations: Fix `get_pep_zero_embed` docstring --- bot/cogs/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 80cdd9210..09c17dbff 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -231,7 +231,7 @@ class Utils(Cog): @staticmethod def get_pep_zero_embed() -> Embed: - """Send information about PEP 0.""" + """Get information embed about PEP 0.""" pep_embed = Embed( title=f"**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", description=f"[Link](https://www.python.org/dev/peps/)" -- cgit v1.2.3 From 811ee70da17654a00d6ae3fbf32261b3e4f4c784 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 15 May 2020 09:20:47 +0300 Subject: PEP Improvisations: Fix `get_pep_embed` docstring --- bot/cogs/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 09c17dbff..6871ba44c 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -245,7 +245,7 @@ class Utils(Cog): @async_cache(arg_offset=1) async def get_pep_embed(self, pep_nr: int) -> Tuple[Embed, bool]: - """Fetch, generate and return PEP embed. Implement `async_cache`.""" + """Fetch, generate and return PEP embed.""" if pep_nr not in self.peps: log.trace(f"PEP {pep_nr} was not found") not_found = f"PEP {pep_nr} does not exist." -- cgit v1.2.3 From 3bc2c1b116e4b696b8b2409d0621bde3197d2763 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Fri, 15 May 2020 09:28:46 +0300 Subject: PEP Improvisations: Move errors sending from PEP command to `get_pep_embed` Before this, all error embeds was returned on `get_pep_embed` but now this send this itself and return only correct embed to make checking easier in command. --- bot/cogs/utils.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 6871ba44c..a2f9d362e 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -4,7 +4,7 @@ import re import unicodedata from email.parser import HeaderParser from io import StringIO -from typing import Dict, Tuple, Union +from typing import Dict, Optional, Tuple, Union from discord import Colour, Embed from discord.ext.commands import BadArgument, Cog, Context, command @@ -220,12 +220,11 @@ class Utils(Cog): # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. if pep_number == 0: pep_embed = self.get_pep_zero_embed() - success = True else: - pep_embed, success = await self.get_pep_embed(pep_number) - await ctx.send(embed=pep_embed) + pep_embed = await self.get_pep_embed(pep_number, ctx) - if success: + if pep_embed: + await ctx.send(embed=pep_embed) log.trace(f"PEP {pep_number} getting and sending finished successfully. Increasing stat.") self.bot.stats.incr(f"pep_fetches.{pep_number}") @@ -244,12 +243,15 @@ class Utils(Cog): return pep_embed @async_cache(arg_offset=1) - async def get_pep_embed(self, pep_nr: int) -> Tuple[Embed, bool]: + async def get_pep_embed(self, pep_nr: int, ctx: Context) -> Optional[Embed]: """Fetch, generate and return PEP embed.""" if pep_nr not in self.peps: log.trace(f"PEP {pep_nr} was not found") not_found = f"PEP {pep_nr} does not exist." - return Embed(title="PEP not found", description=not_found, colour=Colour.red()), False + await ctx.send( + embed=Embed(title="PEP not found", description=not_found, colour=Colour.red()) + ) + return response = await self.bot.http_session.get(self.peps[pep_nr]) if response.status == 200: @@ -274,7 +276,7 @@ class Utils(Cog): # embed field values can't contain an empty string if pep_header.get(field, ""): pep_embed.add_field(name=field, value=pep_header[field]) - return pep_embed, True + return pep_embed else: log.trace( f"The user requested PEP {pep_nr}, but the response had an unexpected status code: " @@ -282,7 +284,10 @@ class Utils(Cog): ) error_message = "Unexpected HTTP error during PEP search. Please let us know." - return Embed(title="Unexpected error", description=error_message, colour=Colour.red()), False + await ctx.send( + embed=Embed(title="Unexpected error", description=error_message, colour=Colour.red()) + ) + return def setup(bot: Bot) -> None: -- cgit v1.2.3 From e3be25f8d64db4adec36798700423191916353d8 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 17 May 2020 09:25:37 +0300 Subject: PEP Improvisations: Simplify cache item check on `async_cache` decorator Co-authored-by: Mark --- bot/utils/cache.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/utils/cache.py b/bot/utils/cache.py index 338924df8..1c0935faa 100644 --- a/bot/utils/cache.py +++ b/bot/utils/cache.py @@ -21,8 +21,7 @@ def async_cache(max_size: int = 128, arg_offset: int = 0) -> Callable: """Decorator wrapper for the caching logic.""" key = ':'.join(str(args[arg_offset:])) - value = async_cache.cache.get(key) - if value is None: + if key in async_cache.cache: if len(async_cache.cache) > max_size: async_cache.cache.popitem(last=False) -- cgit v1.2.3 From fb27b234a92e4572697a973b3f151863bb13bea1 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 17 May 2020 10:32:14 +0300 Subject: PEP Improvisations: Fix formatting of blocks Added newline before logging after indention block. Co-authored-by: Mark --- bot/cogs/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index a2f9d362e..12f7204fc 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -202,6 +202,7 @@ class Utils(Cog): async with self.bot.http_session.get(self.peps_listing_api_url) as resp: listing = await resp.json() + log.trace("Got PEP URLs listing from GitHub API") for file in listing: @@ -209,6 +210,7 @@ class Utils(Cog): if name.startswith("pep-") and name.endswith((".rst", ".txt")): pep_number = name.replace("pep-", "").split(".")[0] self.peps[int(pep_number)] = file["download_url"] + log.info("Successfully refreshed PEP URLs listing.") @command(name='pep', aliases=('get_pep', 'p')) -- cgit v1.2.3 From 7d0f56917c10709a951f579a030177701ea66339 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 17 May 2020 10:34:38 +0300 Subject: PEP Improvisations: Remove response from logging to avoid newline --- bot/cogs/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 12f7204fc..fac2af721 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -281,8 +281,7 @@ class Utils(Cog): return pep_embed else: log.trace( - f"The user requested PEP {pep_nr}, but the response had an unexpected status code: " - f"{response.status}.\n{response.text}" + f"The user requested PEP {pep_nr}, but the response had an unexpected status code: {response.status}." ) error_message = "Unexpected HTTP error during PEP search. Please let us know." -- cgit v1.2.3 From 50ee35da9cf759094bd73d9c17a77283c1dd7547 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 17 May 2020 10:37:55 +0300 Subject: PEP Improvisations: Move error embed to variables instead creating on `ctx.send` --- bot/cogs/utils.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index fac2af721..303a8c1fb 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -250,10 +250,10 @@ class Utils(Cog): if pep_nr not in self.peps: log.trace(f"PEP {pep_nr} was not found") not_found = f"PEP {pep_nr} does not exist." - await ctx.send( - embed=Embed(title="PEP not found", description=not_found, colour=Colour.red()) - ) + embed = Embed(title="PEP not found", description=not_found, colour=Colour.red()) + await ctx.send(embed=embed) return + response = await self.bot.http_session.get(self.peps[pep_nr]) if response.status == 200: @@ -285,9 +285,8 @@ class Utils(Cog): ) error_message = "Unexpected HTTP error during PEP search. Please let us know." - await ctx.send( - embed=Embed(title="Unexpected error", description=error_message, colour=Colour.red()) - ) + embed = Embed(title="Unexpected error", description=error_message, colour=Colour.red()) + await ctx.send(embed=embed) return -- cgit v1.2.3 From d3072a23d460524e9bb64b8724afbd0b2c44e305 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 17 May 2020 10:58:54 +0300 Subject: PEP Improvisations: Fix cache if statement Add `not` in check is key exist in cache. --- bot/utils/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/utils/cache.py b/bot/utils/cache.py index 1c0935faa..96e1aef95 100644 --- a/bot/utils/cache.py +++ b/bot/utils/cache.py @@ -21,7 +21,7 @@ def async_cache(max_size: int = 128, arg_offset: int = 0) -> Callable: """Decorator wrapper for the caching logic.""" key = ':'.join(str(args[arg_offset:])) - if key in async_cache.cache: + if key not in async_cache.cache: if len(async_cache.cache) > max_size: async_cache.cache.popitem(last=False) -- cgit v1.2.3 From 0f0faa06b60a12a965065f82e6400bab31ab1284 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 17 May 2020 11:01:16 +0300 Subject: PEP Improvisations: Remove PEP URLs refreshing task + replace it with new system Now PEP command request PEP listing when PEP is not found and last refresh was more time ago than 30 minutes instead task. --- bot/cogs/utils.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 303a8c1fb..55164faf1 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -2,13 +2,13 @@ import difflib import logging import re import unicodedata +from datetime import datetime, timedelta from email.parser import HeaderParser from io import StringIO from typing import Dict, Optional, Tuple, Union from discord import Colour, Embed from discord.ext.commands import BadArgument, Cog, Context, command -from discord.ext.tasks import loop from bot.bot import Bot from bot.constants import Channels, MODERATION_ROLES, STAFF_ROLES @@ -53,7 +53,8 @@ class Utils(Cog): self.peps_listing_api_url = "https://api.github.com/repos/python/peps/contents?ref=master" self.peps: Dict[int, str] = {} - self.refresh_peps_urls.start() + self.last_refreshed_peps: Optional[datetime] = None + self.bot.loop.create_task(self.refresh_peps_urls()) @command() @in_whitelist(channels=(Channels.bot_commands,), roles=STAFF_ROLES) @@ -193,7 +194,6 @@ class Utils(Cog): # PEPs area - @loop(hours=3) async def refresh_peps_urls(self) -> None: """Refresh PEP URLs listing in every 3 hours.""" # Wait until HTTP client is available @@ -211,6 +211,7 @@ class Utils(Cog): pep_number = name.replace("pep-", "").split(".")[0] self.peps[int(pep_number)] = file["download_url"] + self.last_refreshed_peps = datetime.now() log.info("Successfully refreshed PEP URLs listing.") @command(name='pep', aliases=('get_pep', 'p')) @@ -223,7 +224,7 @@ class Utils(Cog): if pep_number == 0: pep_embed = self.get_pep_zero_embed() else: - pep_embed = await self.get_pep_embed(pep_number, ctx) + pep_embed = await self.get_pep_embed(ctx, pep_number) if pep_embed: await ctx.send(embed=pep_embed) @@ -244,15 +245,20 @@ class Utils(Cog): return pep_embed - @async_cache(arg_offset=1) - async def get_pep_embed(self, pep_nr: int, ctx: Context) -> Optional[Embed]: + @async_cache(arg_offset=2) + async def get_pep_embed(self, ctx: Context, pep_nr: int) -> Optional[Embed]: """Fetch, generate and return PEP embed.""" - if pep_nr not in self.peps: - log.trace(f"PEP {pep_nr} was not found") - not_found = f"PEP {pep_nr} does not exist." - embed = Embed(title="PEP not found", description=not_found, colour=Colour.red()) - await ctx.send(embed=embed) - return + while True: + if pep_nr not in self.peps and (self.last_refreshed_peps + timedelta(minutes=30)) > datetime.now(): + log.trace(f"PEP {pep_nr} was not found") + not_found = f"PEP {pep_nr} does not exist." + embed = Embed(title="PEP not found", description=not_found, colour=Colour.red()) + await ctx.send(embed=embed) + return + elif pep_nr not in self.peps: + await self.refresh_peps_urls() + else: + break response = await self.bot.http_session.get(self.peps[pep_nr]) -- cgit v1.2.3 From 65c07cc96b8309c9002b87a07a7ebdbb9538342a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 18 May 2020 08:13:21 +0300 Subject: PEP: Removed `while` loop from refresh checking on `get_pep_embed` --- bot/cogs/utils.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 55164faf1..73337f012 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -248,17 +248,15 @@ class Utils(Cog): @async_cache(arg_offset=2) async def get_pep_embed(self, ctx: Context, pep_nr: int) -> Optional[Embed]: """Fetch, generate and return PEP embed.""" - while True: - if pep_nr not in self.peps and (self.last_refreshed_peps + timedelta(minutes=30)) > datetime.now(): - log.trace(f"PEP {pep_nr} was not found") - not_found = f"PEP {pep_nr} does not exist." - embed = Embed(title="PEP not found", description=not_found, colour=Colour.red()) - await ctx.send(embed=embed) - return - elif pep_nr not in self.peps: - await self.refresh_peps_urls() - else: - break + if pep_nr not in self.peps and (self.last_refreshed_peps + timedelta(minutes=30)) <= datetime.now(): + await self.refresh_peps_urls() + + if pep_nr not in self.peps: + log.trace(f"PEP {pep_nr} was not found") + not_found = f"PEP {pep_nr} does not exist." + embed = Embed(title="PEP not found", description=not_found, colour=Colour.red()) + await ctx.send(embed=embed) + return response = await self.bot.http_session.get(self.peps[pep_nr]) -- cgit v1.2.3 From 674d976b706ff42039ea1ea12e0b6150f180e874 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Jun 2020 18:24:40 +0300 Subject: PEP: Define PEP region for grouping functions --- bot/cogs/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 73337f012..d4015e235 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -192,7 +192,7 @@ class Utils(Cog): for reaction in options: await message.add_reaction(reaction) - # PEPs area + # region: PEP async def refresh_peps_urls(self) -> None: """Refresh PEP URLs listing in every 3 hours.""" @@ -292,6 +292,7 @@ class Utils(Cog): embed = Embed(title="Unexpected error", description=error_message, colour=Colour.red()) await ctx.send(embed=embed) return + # endregion def setup(bot: Bot) -> None: -- cgit v1.2.3 From 61ccb8230913e3eff8285c1387c20354e15fcc55 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Jun 2020 18:44:53 +0300 Subject: Async Cache: Make cache handle different caches better --- bot/cogs/doc.py | 2 +- bot/utils/cache.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bot/cogs/doc.py b/bot/cogs/doc.py index ff60fc80a..173585976 100644 --- a/bot/cogs/doc.py +++ b/bot/cogs/doc.py @@ -187,7 +187,7 @@ class Doc(commands.Cog): self.base_urls.clear() self.inventories.clear() self.renamed_symbols.clear() - async_cache.cache = OrderedDict() + async_cache.cache["get_symbol_embed"] = OrderedDict() # Run all coroutines concurrently - since each of them performs a HTTP # request, this speeds up fetching the inventory data heavily. diff --git a/bot/utils/cache.py b/bot/utils/cache.py index 96e1aef95..37c2b199c 100644 --- a/bot/utils/cache.py +++ b/bot/utils/cache.py @@ -11,21 +11,23 @@ def async_cache(max_size: int = 128, arg_offset: int = 0) -> Callable: 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() + # Make global cache as dictionary to allow multiple function caches + async_cache.cache = {} def decorator(function: Callable) -> Callable: """Define the async_cache decorator.""" + async_cache.cache[function.__name__] = OrderedDict() + @functools.wraps(function) async def wrapper(*args) -> Any: """Decorator wrapper for the caching logic.""" key = ':'.join(str(args[arg_offset:])) if key not in async_cache.cache: - if len(async_cache.cache) > max_size: - async_cache.cache.popitem(last=False) + if len(async_cache.cache[function.__name__]) > max_size: + async_cache.cache[function.__name__].popitem(last=False) - async_cache.cache[key] = await function(*args) - return async_cache.cache[key] + async_cache.cache[function.__name__][key] = await function(*args) + return async_cache.cache[function.__name__][key] return wrapper return decorator -- cgit v1.2.3 From df3142485af13605d9663b055c39e558f7149a0f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Jun 2020 18:48:34 +0300 Subject: PEP: Filter out too big PEP numbers --- bot/cogs/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index d4015e235..7dbc5b014 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -248,7 +248,11 @@ class Utils(Cog): @async_cache(arg_offset=2) async def get_pep_embed(self, ctx: Context, pep_nr: int) -> Optional[Embed]: """Fetch, generate and return PEP embed.""" - if pep_nr not in self.peps and (self.last_refreshed_peps + timedelta(minutes=30)) <= datetime.now(): + if ( + pep_nr not in self.peps + and (self.last_refreshed_peps + timedelta(minutes=30)) <= datetime.now() + and len(str(pep_nr)) < 5 + ): await self.refresh_peps_urls() if pep_nr not in self.peps: -- cgit v1.2.3 From 3be09a656d0d904187306b1e15fd02c22378b265 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 22 Jun 2020 18:54:26 +0300 Subject: PEP: Move PEP error message sending to another function --- bot/cogs/utils.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 7dbc5b014..2605a6226 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -247,7 +247,7 @@ class Utils(Cog): @async_cache(arg_offset=2) async def get_pep_embed(self, ctx: Context, pep_nr: int) -> Optional[Embed]: - """Fetch, generate and return PEP embed.""" + """Fetch, generate and return PEP embed. When any error occur, use `self.send_pep_error_embed`.""" if ( pep_nr not in self.peps and (self.last_refreshed_peps + timedelta(minutes=30)) <= datetime.now() @@ -258,9 +258,7 @@ class Utils(Cog): if pep_nr not in self.peps: log.trace(f"PEP {pep_nr} was not found") not_found = f"PEP {pep_nr} does not exist." - embed = Embed(title="PEP not found", description=not_found, colour=Colour.red()) - await ctx.send(embed=embed) - return + return await self.send_pep_error_embed(ctx, "PEP not found", not_found) response = await self.bot.http_session.get(self.peps[pep_nr]) @@ -291,11 +289,14 @@ class Utils(Cog): log.trace( f"The user requested PEP {pep_nr}, but the response had an unexpected status code: {response.status}." ) - error_message = "Unexpected HTTP error during PEP search. Please let us know." - embed = Embed(title="Unexpected error", description=error_message, colour=Colour.red()) - await ctx.send(embed=embed) - return + return await self.send_pep_error_embed(ctx, "Unexpected error", error_message) + + @staticmethod + async def send_pep_error_embed(ctx: Context, title: str, description: str) -> None: + """Send error PEP embed with `ctx.send`.""" + embed = Embed(title=title, description=description, colour=Colour.red()) + await ctx.send(embed=embed) # endregion -- cgit v1.2.3 From fd955f61447dc5401629a9312d4a86e3cbe68693 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 26 Sep 2020 15:43:09 +0300 Subject: Async Cache: Create class-based async cache --- bot/exts/info/doc.py | 7 +++++-- bot/exts/utils/utils.py | 5 ++++- bot/utils/cache.py | 49 ++++++++++++++++++++++++++++--------------------- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/bot/exts/info/doc.py b/bot/exts/info/doc.py index 06dd4df63..ba443d817 100644 --- a/bot/exts/info/doc.py +++ b/bot/exts/info/doc.py @@ -22,7 +22,7 @@ from bot.bot import Bot from bot.constants import MODERATION_ROLES, RedirectOutput from bot.converters import ValidPythonIdentifier, ValidURL from bot.pagination import LinePaginator -from bot.utils.cache import async_cache +from bot.utils.cache import AsyncCache from bot.utils.messages import wait_for_deletion @@ -66,6 +66,9 @@ WHITESPACE_AFTER_NEWLINES_RE = re.compile(r"(?<=\n\n)(\s+)") FAILED_REQUEST_RETRY_AMOUNT = 3 NOT_FOUND_DELETE_DELAY = RedirectOutput.delete_delay +# Async cache instance for docs cog +async_cache = AsyncCache() + class DocMarkdownConverter(MarkdownConverter): """Subclass markdownify's MarkdownCoverter to provide custom conversion methods.""" @@ -187,7 +190,7 @@ class Doc(commands.Cog): self.base_urls.clear() self.inventories.clear() self.renamed_symbols.clear() - async_cache.cache["get_symbol_embed"] = OrderedDict() + async_cache.clear() # Run all coroutines concurrently - since each of them performs a HTTP # request, this speeds up fetching the inventory data heavily. diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index 2a74af172..64d42c93e 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -15,7 +15,7 @@ from bot.constants import Channels, MODERATION_ROLES, STAFF_ROLES from bot.decorators import in_whitelist from bot.pagination import LinePaginator from bot.utils import messages -from bot.utils.cache import async_cache +from bot.utils.cache import AsyncCache log = logging.getLogger(__name__) @@ -43,6 +43,9 @@ Namespaces are one honking great idea -- let's do more of those! ICON_URL = "https://www.python.org/static/opengraph-icon-200x200.png" +# Async cache instance for PEPs +async_cache = AsyncCache() + class Utils(Cog): """A selection of utilities which don't have a clear category.""" diff --git a/bot/utils/cache.py b/bot/utils/cache.py index 37c2b199c..d8ec64ec8 100644 --- a/bot/utils/cache.py +++ b/bot/utils/cache.py @@ -3,7 +3,7 @@ from collections import OrderedDict from typing import Any, Callable -def async_cache(max_size: int = 128, arg_offset: int = 0) -> Callable: +class AsyncCache: """ LRU cache implementation for coroutines. @@ -11,23 +11,30 @@ def async_cache(max_size: int = 128, arg_offset: int = 0) -> Callable: An offset may be optionally provided to be applied to the coroutine's arguments when creating the cache key. """ - # Make global cache as dictionary to allow multiple function caches - async_cache.cache = {} - - def decorator(function: Callable) -> Callable: - """Define the async_cache decorator.""" - async_cache.cache[function.__name__] = OrderedDict() - - @functools.wraps(function) - async def wrapper(*args) -> Any: - """Decorator wrapper for the caching logic.""" - key = ':'.join(str(args[arg_offset:])) - - if key not in async_cache.cache: - if len(async_cache.cache[function.__name__]) > max_size: - async_cache.cache[function.__name__].popitem(last=False) - - async_cache.cache[function.__name__][key] = await function(*args) - return async_cache.cache[function.__name__][key] - return wrapper - return decorator + + def __init__(self): + self._cache = OrderedDict() + + def __call__(self, max_size: int = 128, arg_offset: int = 0) -> Callable: + """Decorator for async cache.""" + + def decorator(function: Callable) -> Callable: + """Define the async cache decorator.""" + + @functools.wraps(function) + async def wrapper(*args) -> Any: + """Decorator wrapper for the caching logic.""" + key = ':'.join(str(args[arg_offset:])) + + if key not in self._cache: + if len(self._cache) > max_size: + self._cache.popitem(last=False) + + self._cache[key] = await function(*args) + return self._cache[key] + return wrapper + return decorator + + def clear(self): + """Clear cache instance.""" + self._cache.clear() -- cgit v1.2.3 From 17cf8278ff9768f194bf74980507361b0a13af03 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 26 Sep 2020 15:54:32 +0300 Subject: PEP: Split get_pep_embed to smaller parts --- bot/exts/utils/utils.py | 56 ++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index 64d42c93e..cc284ec5a 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -7,7 +7,7 @@ from email.parser import HeaderParser from io import StringIO from typing import Dict, Optional, Tuple, Union -from discord import Colour, Embed, utils +from discord import Colour, Embed, Message, utils from discord.ext.commands import BadArgument, Cog, Context, clean_content, command, has_any_role from bot.bot import Bot @@ -223,6 +223,9 @@ class Utils(Cog): if pep_number == 0: pep_embed = self.get_pep_zero_embed() else: + if not await self.validate_pep_number(ctx, pep_number): + return + pep_embed = await self.get_pep_embed(ctx, pep_number) if pep_embed: @@ -244,9 +247,8 @@ class Utils(Cog): return pep_embed - @async_cache(arg_offset=2) - async def get_pep_embed(self, ctx: Context, pep_nr: int) -> Optional[Embed]: - """Fetch, generate and return PEP embed. When any error occur, use `self.send_pep_error_embed`.""" + async def validate_pep_number(self, ctx: Context, pep_nr: int) -> bool: + """Validate is PEP number valid. When it isn't, send error and return False. Otherwise return True.""" if ( pep_nr not in self.peps and (self.last_refreshed_peps + timedelta(minutes=30)) <= datetime.now() @@ -257,8 +259,34 @@ class Utils(Cog): if pep_nr not in self.peps: log.trace(f"PEP {pep_nr} was not found") not_found = f"PEP {pep_nr} does not exist." - return await self.send_pep_error_embed(ctx, "PEP not found", not_found) + await self.send_pep_error_embed(ctx, "PEP not found", not_found) + return False + + return True + + def generate_pep_embed(self, pep_header: Dict, pep_nr: int) -> Embed: + """Generate PEP embed based on PEP headers data.""" + # Assemble the embed + pep_embed = Embed( + title=f"**PEP {pep_nr} - {pep_header['Title']}**", + description=f"[Link]({self.base_pep_url}{pep_nr:04})", + ) + + pep_embed.set_thumbnail(url=ICON_URL) + # Add the interesting information + fields_to_check = ("Status", "Python-Version", "Created", "Type") + for field in fields_to_check: + # Check for a PEP metadata field that is present but has an empty value + # embed field values can't contain an empty string + if pep_header.get(field, ""): + pep_embed.add_field(name=field, value=pep_header[field]) + + return pep_embed + + @async_cache(arg_offset=2) + async def get_pep_embed(self, ctx: Context, pep_nr: int) -> Optional[Embed]: + """Fetch, generate and return PEP embed. When any error occur, use `self.send_pep_error_embed`.""" response = await self.bot.http_session.get(self.peps[pep_nr]) if response.status == 200: @@ -267,23 +295,7 @@ class Utils(Cog): # Taken from https://github.com/python/peps/blob/master/pep0/pep.py#L179 pep_header = HeaderParser().parse(StringIO(pep_content)) - - # Assemble the embed - pep_embed = Embed( - title=f"**PEP {pep_nr} - {pep_header['Title']}**", - description=f"[Link]({self.base_pep_url}{pep_nr:04})", - ) - - pep_embed.set_thumbnail(url=ICON_URL) - - # Add the interesting information - fields_to_check = ("Status", "Python-Version", "Created", "Type") - for field in fields_to_check: - # Check for a PEP metadata field that is present but has an empty value - # embed field values can't contain an empty string - if pep_header.get(field, ""): - pep_embed.add_field(name=field, value=pep_header[field]) - return pep_embed + return self.generate_pep_embed(pep_header, pep_nr) else: log.trace( f"The user requested PEP {pep_nr}, but the response had an unexpected status code: {response.status}." -- cgit v1.2.3 From 3e66482d026490654af1a5a24e96b44bdc804af2 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 26 Sep 2020 15:57:30 +0300 Subject: Fix linting --- bot/exts/info/doc.py | 1 - bot/exts/utils/utils.py | 2 +- bot/utils/cache.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bot/exts/info/doc.py b/bot/exts/info/doc.py index ba443d817..1fd0ee266 100644 --- a/bot/exts/info/doc.py +++ b/bot/exts/info/doc.py @@ -3,7 +3,6 @@ import functools import logging import re import textwrap -from collections import OrderedDict from contextlib import suppress from types import SimpleNamespace from typing import Optional, Tuple diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index cc284ec5a..278b6fefb 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -7,7 +7,7 @@ from email.parser import HeaderParser from io import StringIO from typing import Dict, Optional, Tuple, Union -from discord import Colour, Embed, Message, utils +from discord import Colour, Embed, utils from discord.ext.commands import BadArgument, Cog, Context, clean_content, command, has_any_role from bot.bot import Bot diff --git a/bot/utils/cache.py b/bot/utils/cache.py index d8ec64ec8..70925b71d 100644 --- a/bot/utils/cache.py +++ b/bot/utils/cache.py @@ -35,6 +35,6 @@ class AsyncCache: return wrapper return decorator - def clear(self): + def clear(self) -> None: """Clear cache instance.""" self._cache.clear() -- cgit v1.2.3 From 03560d855ec407d1cfb444f392934bdb53ad5d96 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 4 Oct 2020 09:12:12 +0300 Subject: Rename async cache instances --- bot/exts/info/doc.py | 7 +++---- bot/exts/utils/utils.py | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bot/exts/info/doc.py b/bot/exts/info/doc.py index 1fd0ee266..a847f1440 100644 --- a/bot/exts/info/doc.py +++ b/bot/exts/info/doc.py @@ -65,8 +65,7 @@ WHITESPACE_AFTER_NEWLINES_RE = re.compile(r"(?<=\n\n)(\s+)") FAILED_REQUEST_RETRY_AMOUNT = 3 NOT_FOUND_DELETE_DELAY = RedirectOutput.delete_delay -# Async cache instance for docs cog -async_cache = AsyncCache() +symbol_cache = AsyncCache() class DocMarkdownConverter(MarkdownConverter): @@ -189,7 +188,7 @@ class Doc(commands.Cog): self.base_urls.clear() self.inventories.clear() self.renamed_symbols.clear() - async_cache.clear() + symbol_cache.clear() # Run all coroutines concurrently - since each of them performs a HTTP # request, this speeds up fetching the inventory data heavily. @@ -254,7 +253,7 @@ class Doc(commands.Cog): return signatures, description.replace('ΒΆ', '') - @async_cache(arg_offset=1) + @symbol_cache(arg_offset=1) async def get_symbol_embed(self, symbol: str) -> Optional[discord.Embed]: """ Attempt to scrape and fetch the data for the given `symbol`, and build an embed from its contents. diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index 278b6fefb..c006fb87e 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -43,8 +43,7 @@ Namespaces are one honking great idea -- let's do more of those! ICON_URL = "https://www.python.org/static/opengraph-icon-200x200.png" -# Async cache instance for PEPs -async_cache = AsyncCache() +pep_cache = AsyncCache() class Utils(Cog): @@ -284,7 +283,7 @@ class Utils(Cog): return pep_embed - @async_cache(arg_offset=2) + @pep_cache(arg_offset=2) async def get_pep_embed(self, ctx: Context, pep_nr: int) -> Optional[Embed]: """Fetch, generate and return PEP embed. When any error occur, use `self.send_pep_error_embed`.""" response = await self.bot.http_session.get(self.peps[pep_nr]) -- cgit v1.2.3 From 86f2024b38f2b7d017a7a68300c3a7f4b79aab45 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 4 Oct 2020 09:17:30 +0300 Subject: Move PEP URLs to class constants --- bot/exts/utils/utils.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index c006fb87e..0d16a142e 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -49,13 +49,12 @@ pep_cache = AsyncCache() class Utils(Cog): """A selection of utilities which don't have a clear category.""" + BASE_PEP_URL = "http://www.python.org/dev/peps/pep-" + BASE_GITHUB_PEP_URL = "https://raw.githubusercontent.com/python/peps/master/pep-" + PEPS_LISTING_API_URL = "https://api.github.com/repos/python/peps/contents?ref=master" + 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-" - self.peps_listing_api_url = "https://api.github.com/repos/python/peps/contents?ref=master" - self.peps: Dict[int, str] = {} self.last_refreshed_peps: Optional[datetime] = None self.bot.loop.create_task(self.refresh_peps_urls()) @@ -198,7 +197,7 @@ class Utils(Cog): await self.bot.wait_until_ready() log.trace("Started refreshing PEP URLs.") - async with self.bot.http_session.get(self.peps_listing_api_url) as resp: + async with self.bot.http_session.get(self.PEPS_LISTING_API_URL) as resp: listing = await resp.json() log.trace("Got PEP URLs listing from GitHub API") @@ -268,7 +267,7 @@ class Utils(Cog): # Assemble the embed pep_embed = Embed( title=f"**PEP {pep_nr} - {pep_header['Title']}**", - description=f"[Link]({self.base_pep_url}{pep_nr:04})", + description=f"[Link]({self.BASE_PEP_URL}{pep_nr:04})", ) pep_embed.set_thumbnail(url=ICON_URL) -- cgit v1.2.3 From c58d68eed338514963525099c233363f01db1e65 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 4 Oct 2020 09:24:08 +0300 Subject: Make AsyncCache key tuple instead string --- bot/utils/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/utils/cache.py b/bot/utils/cache.py index 70925b71d..8a180b4fa 100644 --- a/bot/utils/cache.py +++ b/bot/utils/cache.py @@ -24,7 +24,7 @@ class AsyncCache: @functools.wraps(function) async def wrapper(*args) -> Any: """Decorator wrapper for the caching logic.""" - key = ':'.join(str(args[arg_offset:])) + key = args[arg_offset:] if key not in self._cache: if len(self._cache) > max_size: -- cgit v1.2.3 From 90103c58697889fdd352cd021faba6be2ad3a7d7 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 4 Oct 2020 09:25:52 +0300 Subject: Move AsyncCache max_size argument to __init__ from decorator --- bot/utils/cache.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bot/utils/cache.py b/bot/utils/cache.py index 8a180b4fa..68ce15607 100644 --- a/bot/utils/cache.py +++ b/bot/utils/cache.py @@ -12,10 +12,11 @@ class AsyncCache: An offset may be optionally provided to be applied to the coroutine's arguments when creating the cache key. """ - def __init__(self): + def __init__(self, max_size: int = 128): self._cache = OrderedDict() + self._max_size = max_size - def __call__(self, max_size: int = 128, arg_offset: int = 0) -> Callable: + def __call__(self, arg_offset: int = 0) -> Callable: """Decorator for async cache.""" def decorator(function: Callable) -> Callable: @@ -27,7 +28,7 @@ class AsyncCache: key = args[arg_offset:] if key not in self._cache: - if len(self._cache) > max_size: + if len(self._cache) > self._max_size: self._cache.popitem(last=False) self._cache[key] = await function(*args) -- cgit v1.2.3 From c9ffb11c440482de3cb9c46c746d213e974ea754 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 18 Oct 2020 09:06:17 +0300 Subject: Refactor PEP error embed sending --- bot/exts/utils/utils.py | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index 558d0cf72..e134a0994 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -220,16 +220,18 @@ class Utils(Cog): # Handle PEP 0 directly because it's not in .rst or .txt so it can't be accessed like other PEPs. if pep_number == 0: pep_embed = self.get_pep_zero_embed() + success = True else: - if not await self.validate_pep_number(ctx, pep_number): - return + success = False + if not (pep_embed := await self.validate_pep_number(pep_number)): + pep_embed, success = await self.get_pep_embed(pep_number) - pep_embed = await self.get_pep_embed(ctx, pep_number) - - if pep_embed: - await ctx.send(embed=pep_embed) + await ctx.send(embed=pep_embed) + if success: log.trace(f"PEP {pep_number} getting and sending finished successfully. Increasing stat.") self.bot.stats.incr(f"pep_fetches.{pep_number}") + else: + log.trace(f"Getting PEP {pep_number} failed. Error embed sent.") @staticmethod def get_pep_zero_embed() -> Embed: @@ -245,8 +247,8 @@ class Utils(Cog): return pep_embed - async def validate_pep_number(self, ctx: Context, pep_nr: int) -> bool: - """Validate is PEP number valid. When it isn't, send error and return False. Otherwise return True.""" + async def validate_pep_number(self, pep_nr: int) -> Optional[Embed]: + """Validate is PEP number valid. When it isn't, return error embed, otherwise None.""" if ( pep_nr not in self.peps and (self.last_refreshed_peps + timedelta(minutes=30)) <= datetime.now() @@ -256,11 +258,13 @@ class Utils(Cog): if pep_nr not in self.peps: log.trace(f"PEP {pep_nr} was not found") - not_found = f"PEP {pep_nr} does not exist." - await self.send_pep_error_embed(ctx, "PEP not found", not_found) - return False + return Embed( + title="PEP not found", + description=f"PEP {pep_nr} does not exist.", + colour=Colour.red() + ) - return True + return None def generate_pep_embed(self, pep_header: Dict, pep_nr: int) -> Embed: """Generate PEP embed based on PEP headers data.""" @@ -283,8 +287,8 @@ class Utils(Cog): return pep_embed @pep_cache(arg_offset=2) - async def get_pep_embed(self, ctx: Context, pep_nr: int) -> Optional[Embed]: - """Fetch, generate and return PEP embed. When any error occur, use `self.send_pep_error_embed`.""" + async def get_pep_embed(self, pep_nr: int) -> Tuple[Embed, bool]: + """Fetch, generate and return PEP embed. Second item of return tuple show does getting success.""" response = await self.bot.http_session.get(self.peps[pep_nr]) if response.status == 200: @@ -293,19 +297,16 @@ class Utils(Cog): # Taken from https://github.com/python/peps/blob/master/pep0/pep.py#L179 pep_header = HeaderParser().parse(StringIO(pep_content)) - return self.generate_pep_embed(pep_header, pep_nr) + return self.generate_pep_embed(pep_header, pep_nr), True else: log.trace( f"The user requested PEP {pep_nr}, but the response had an unexpected status code: {response.status}." ) - error_message = "Unexpected HTTP error during PEP search. Please let us know." - return await self.send_pep_error_embed(ctx, "Unexpected error", error_message) - - @staticmethod - async def send_pep_error_embed(ctx: Context, title: str, description: str) -> None: - """Send error PEP embed with `ctx.send`.""" - embed = Embed(title=title, description=description, colour=Colour.red()) - await ctx.send(embed=embed) + return Embed( + title="Unexpected error", + description="Unexpected HTTP error during PEP search. Please let us know.", + colour=Colour.red() + ), False # endregion -- cgit v1.2.3 From 456a6fbf76baf7cfdcbd21864c0d410297acc1ff Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 18 Oct 2020 09:10:54 +0300 Subject: Fix argument offset --- bot/exts/utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py index e134a0994..6d8d98695 100644 --- a/bot/exts/utils/utils.py +++ b/bot/exts/utils/utils.py @@ -286,7 +286,7 @@ class Utils(Cog): return pep_embed - @pep_cache(arg_offset=2) + @pep_cache(arg_offset=1) async def get_pep_embed(self, pep_nr: int) -> Tuple[Embed, bool]: """Fetch, generate and return PEP embed. Second item of return tuple show does getting success.""" response = await self.bot.http_session.get(self.peps[pep_nr]) -- cgit v1.2.3 From 765961e784ca5c1d949bd4b02251d3d3132760a8 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sat, 31 Oct 2020 16:06:42 +0000 Subject: Add new activity block constant --- bot/constants.py | 1 + config-default.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index 23d5b4304..4d41f4eb2 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -600,6 +600,7 @@ class VoiceGate(metaclass=YAMLGetter): minimum_days_verified: int minimum_messages: int bot_message_delete_delay: int + minimum_activity_blocks: int class Event(Enum): diff --git a/config-default.yml b/config-default.yml index 071f6e1ec..a2cabf5fc 100644 --- a/config-default.yml +++ b/config-default.yml @@ -521,6 +521,7 @@ voice_gate: minimum_days_verified: 3 # How many days the user must have been verified for minimum_messages: 50 # How many messages a user must have to be eligible for voice bot_message_delete_delay: 10 # Seconds before deleting bot's response in Voice Gate + minimum_activity_blocks: 3 # Number of 10 minute blocks during which a user must have been active config: -- cgit v1.2.3 From 732d526807021f0e273840d31b4dff39eb8fe0bb Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sat, 31 Oct 2020 16:12:44 +0000 Subject: Add activity blocks threshold to voice gate --- bot/exts/moderation/voice_gate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py index c2743e136..b9ddc1093 100644 --- a/bot/exts/moderation/voice_gate.py +++ b/bot/exts/moderation/voice_gate.py @@ -25,6 +25,7 @@ MESSAGE_FIELD_MAP = { "verified_at": f"have been verified for less than {GateConf.minimum_days_verified} days", "voice_banned": "have an active voice ban infraction", "total_messages": f"have sent less than {GateConf.minimum_messages} messages", + "activity_blocks": f"have been active for less than {GateConf.minimum_activity_blocks} ten-minute blocks" } @@ -50,6 +51,7 @@ class VoiceGate(Cog): - You must have over a certain number of messages within the Discord server - You must have accepted our rules over a certain number of days ago - You must not be actively banned from using our voice channels + - You must have been active for over a certain number of 10-minute blocks. """ try: data = await self.bot.api_client.get(f"bot/users/{ctx.author.id}/metricity_data") @@ -88,7 +90,8 @@ class VoiceGate(Cog): checks = { "verified_at": data["verified_at"] > datetime.utcnow() - timedelta(days=GateConf.minimum_days_verified), "total_messages": data["total_messages"] < GateConf.minimum_messages, - "voice_banned": data["voice_banned"] + "voice_banned": data["voice_banned"], + "activity_blocks": data["activity_blocks"] < GateConf.activity_blocks } failed = any(checks.values()) failed_reasons = [MESSAGE_FIELD_MAP[key] for key, value in checks.items() if value is True] -- cgit v1.2.3 From 54fd8c03aaf2cf7509867a223c5b54366bd8f1e0 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 1 Nov 2020 02:27:07 +0000 Subject: Remove full stop --- bot/exts/moderation/voice_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py index b9ddc1093..cf64c4e52 100644 --- a/bot/exts/moderation/voice_gate.py +++ b/bot/exts/moderation/voice_gate.py @@ -51,7 +51,7 @@ class VoiceGate(Cog): - You must have over a certain number of messages within the Discord server - You must have accepted our rules over a certain number of days ago - You must not be actively banned from using our voice channels - - You must have been active for over a certain number of 10-minute blocks. + - You must have been active for over a certain number of 10-minute blocks """ try: data = await self.bot.api_client.get(f"bot/users/{ctx.author.id}/metricity_data") -- cgit v1.2.3 From e0fba54b56d9fc7a7acfc7d7651f9b34b9e0712f Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 1 Nov 2020 02:28:17 +0000 Subject: Change wording of failure message and re-add trailing comma --- bot/exts/moderation/voice_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py index cf64c4e52..78fc1e619 100644 --- a/bot/exts/moderation/voice_gate.py +++ b/bot/exts/moderation/voice_gate.py @@ -25,7 +25,7 @@ MESSAGE_FIELD_MAP = { "verified_at": f"have been verified for less than {GateConf.minimum_days_verified} days", "voice_banned": "have an active voice ban infraction", "total_messages": f"have sent less than {GateConf.minimum_messages} messages", - "activity_blocks": f"have been active for less than {GateConf.minimum_activity_blocks} ten-minute blocks" + "activity_blocks": f"have been active for fewer than {GateConf.minimum_activity_blocks} ten-minute blocks", } -- cgit v1.2.3 From f076231f9919c1f9320c48e5e4af7a1ad2a0401f Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 1 Nov 2020 02:28:43 +0000 Subject: Indent inline comment by two spaces in config-default.yml --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index a2cabf5fc..2afdcd594 100644 --- a/config-default.yml +++ b/config-default.yml @@ -521,7 +521,7 @@ voice_gate: minimum_days_verified: 3 # How many days the user must have been verified for minimum_messages: 50 # How many messages a user must have to be eligible for voice bot_message_delete_delay: 10 # Seconds before deleting bot's response in Voice Gate - minimum_activity_blocks: 3 # Number of 10 minute blocks during which a user must have been active + minimum_activity_blocks: 3 # Number of 10 minute blocks during which a user must have been active config: -- cgit v1.2.3 From 52c9d0706da2ab8253894679df1b113f1e4c51ae Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 1 Nov 2020 02:29:30 +0000 Subject: Correct activity block config name in voice gate extension --- bot/exts/moderation/voice_gate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py index 78fc1e619..529dca53d 100644 --- a/bot/exts/moderation/voice_gate.py +++ b/bot/exts/moderation/voice_gate.py @@ -91,7 +91,7 @@ class VoiceGate(Cog): "verified_at": data["verified_at"] > datetime.utcnow() - timedelta(days=GateConf.minimum_days_verified), "total_messages": data["total_messages"] < GateConf.minimum_messages, "voice_banned": data["voice_banned"], - "activity_blocks": data["activity_blocks"] < GateConf.activity_blocks + "activity_blocks": data["activity_blocks"] < GateConf.minimum_activity_blocks } failed = any(checks.values()) failed_reasons = [MESSAGE_FIELD_MAP[key] for key, value in checks.items() if value is True] -- cgit v1.2.3 From 399f286a157b21e860f606b4f4fed11bb29490f2 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 1 Nov 2020 13:53:07 +0000 Subject: Correct 404 error message in voice gate command --- bot/exts/moderation/voice_gate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py index 529dca53d..9fd553441 100644 --- a/bot/exts/moderation/voice_gate.py +++ b/bot/exts/moderation/voice_gate.py @@ -60,8 +60,8 @@ class VoiceGate(Cog): embed = discord.Embed( title="Not found", description=( - "We were unable to find user data for you. " - "Please try again shortly, " + "We were unable to find user data for you. ", + "Please try again shortly, ", "if this problem persists please contact the server staff through Modmail.", ), color=Colour.red() -- cgit v1.2.3 From 176d22c8b3cd9fe226778327f9eb9ff080101a04 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 1 Nov 2020 15:36:16 +0000 Subject: Actually fix the issue @kwzrd pointed out --- bot/exts/moderation/voice_gate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py index 9fd553441..93d96693c 100644 --- a/bot/exts/moderation/voice_gate.py +++ b/bot/exts/moderation/voice_gate.py @@ -60,9 +60,9 @@ class VoiceGate(Cog): embed = discord.Embed( title="Not found", description=( - "We were unable to find user data for you. ", - "Please try again shortly, ", - "if this problem persists please contact the server staff through Modmail.", + "We were unable to find user data for you. " + "Please try again shortly, " + "if this problem persists please contact the server staff through Modmail." ), color=Colour.red() ) -- cgit v1.2.3