diff options
author | 2022-05-02 16:29:26 +0100 | |
---|---|---|
committer | 2025-04-09 09:38:29 +0100 | |
commit | 8f4e7d51a637697f244a430e754aa9370d738e75 (patch) | |
tree | 297b5fdba727a87193834a1c67587c5dc3b52651 | |
parent | Actually used capped duration (diff) |
Simplify PEP cog to use PEP api
-rw-r--r-- | bot/exts/info/pep.py | 170 |
1 files changed, 49 insertions, 121 deletions
diff --git a/bot/exts/info/pep.py b/bot/exts/info/pep.py index 2b552dc4f..e8bb0edc4 100644 --- a/bot/exts/info/pep.py +++ b/bot/exts/info/pep.py @@ -1,26 +1,17 @@ from datetime import UTC, datetime, timedelta -from email.parser import HeaderParser -from io import StringIO +from typing import Optional from discord import Colour, Embed from discord.ext.commands import Cog, Context, command from pydis_core.utils.caching import AsyncCache from bot.bot import Bot -from bot.constants import Keys from bot.log import get_logger log = get_logger(__name__) ICON_URL = "https://www.python.org/static/opengraph-icon-200x200.png" -BASE_PEP_URL = "https://peps.python.org/pep-" -PEPS_LISTING_API_URL = "https://api.github.com/repos/python/peps/contents/peps?ref=main" - -pep_cache = AsyncCache() - -GITHUB_API_HEADERS = {} -if Keys.github: - GITHUB_API_HEADERS["Authorization"] = f"token {Keys.github}" +PEP_API_URL = "https://peps.python.org/api/peps.json" class PythonEnhancementProposals(Cog): @@ -28,136 +19,73 @@ class PythonEnhancementProposals(Cog): def __init__(self, bot: Bot): self.bot = bot - self.peps: dict[int, str] = {} - # Ensure peps are refreshed the first time this is checked - self.last_refreshed_peps: datetime = datetime.min.replace(tzinfo=UTC) + self.peps: dict[int, dict[str, Optional[str]]] = {} + self.last_refreshed_peps = datetime.now() + + async def cog_load(self) -> None: + """Carry out cog asynchronous initialisation.""" + await self.refresh_pep_data() + + async def refresh_pep_data(self) -> None: + """Refresh PEP data.""" + # Putting this first should prevent any race conditions + self.last_refreshed_peps = datetime.now(tz=UTC) - 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_ready() - log.trace("Started refreshing PEP URLs.") - self.last_refreshed_peps = datetime.now(tz=UTC) - async with self.bot.http_session.get( - PEPS_LISTING_API_URL, - headers=GITHUB_API_HEADERS - ) as resp: + log.trace("Started refreshing PEPs data.") + async with self.bot.http_session.get(PEP_API_URL) as resp: if resp.status != 200: - log.warning(f"Fetching PEP URLs from GitHub API failed with code {resp.status}") + log.warning( + f"Fetching PEP data from PEP API failed with code {resp.status}" + ) return - 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((".rst", ".txt")): - pep_number = name.replace("pep-", "").split(".")[0] - self.peps[int(pep_number)] = file["download_url"] + for pep_num, pep_info in listing.items(): + self.peps[int(pep_num)] = pep_info - log.info("Successfully refreshed PEP URLs listing.") + log.info("Successfully refreshed PEP data.") - @staticmethod - def get_pep_zero_embed() -> Embed: - """Get information embed about PEP 0.""" - pep_embed = Embed( - title="**PEP 0 - Index of Python Enhancement Proposals (PEPs)**", - url="https://peps.python.org/" + def generate_pep_embed(self, pep_number: int) -> Embed: + """Generate PEP embed.""" + pep = self.peps[pep_number] + embed = Embed( + title=f"**PEP {pep_number} - {pep['title']}**", + description=f"[Link]({pep['url']})", ) - 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") + embed.set_thumbnail(url=ICON_URL) - return pep_embed + fields_to_check = ("status", "python_version", "created", "type") + for field_name in fields_to_check: + if field_value := pep.get(field_name): + field_name = field_name.replace("_", " ").title() + embed.add_field(name=field_name, value=field_value) - async def validate_pep_number(self, pep_nr: int) -> Embed | None: - """Validate is PEP number valid. When it isn't, return error embed, otherwise None.""" + return embed + + @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.""" if ( - pep_nr not in self.peps - and (self.last_refreshed_peps + timedelta(minutes=30)) <= datetime.now(tz=UTC) - and len(str(pep_nr)) < 5 + pep_number not in self.peps + and (self.last_refreshed_peps + timedelta(minutes=30)) <= datetime.now() + and len(str(pep_number)) < 5 ): await self.refresh_peps_urls() - if pep_nr not in self.peps: - log.trace(f"PEP {pep_nr} was not found") - return Embed( + if pep_number not in self.peps: + log.trace(f"PEP {pep_number} was not found") + embed = Embed( title="PEP not found", - description=f"PEP {pep_nr} does not exist.", - colour=Colour.red() + description=f"PEP {pep_number} does not exist.", + colour=Colour.red(), ) - - return None - - def generate_pep_embed(self, pep_header: dict, pep_nr: int) -> Embed: - """Generate PEP embed based on PEP headers data.""" - # the parsed header can be wrapped to multiple lines, so we need to make sure that is removed - # for an example of a pep with this issue, see pep 500 - title = " ".join(pep_header["Title"].split()) - # Assemble the embed - pep_embed = Embed( - title=f"**PEP {pep_nr} - {title}**", - url=f"{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 - - @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]) - - 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)) - return self.generate_pep_embed(pep_header, pep_nr), True - - log.trace( - f"The user requested PEP {pep_nr}, but the response had an unexpected status code: {response.status}." - ) - return Embed( - title="Unexpected error", - description="Unexpected HTTP error during PEP search. Please let us know.", - colour=Colour.red() - ), False - - @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.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 = self.get_pep_zero_embed() - success = True - else: - success = False - if not (pep_embed := await self.validate_pep_number(pep_number)): - pep_embed, success = await self.get_pep_embed(pep_number) - - 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.") + embed = self.generate_pep_embed(pep_number) + + await ctx.send(embed=embed) async def setup(bot: Bot) -> None: |