diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 050dc42..371cb5a 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -4,6 +4,9 @@ Changelog ========= +- :release:`1.5.1 <17th April 2023>` +- :feature:`30` Replace PyPI parser with letsbuilda-pypi + - :release:`1.5.0 <16th April 2023>` - :feature:`31` Update PyPI package report email to match Inspector's new template diff --git a/pyproject.toml b/pyproject.toml index 67050c2..f125c89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "alessia" -version = "1.5.0" +version = "1.5.1" dependencies = [ "python-dotenv==1.0.0", "discord.py==2.2.2", diff --git a/src/bot/constants.py b/src/bot/constants.py index b4acb34..a9ab62a 100644 --- a/src/bot/constants.py +++ b/src/bot/constants.py @@ -65,7 +65,6 @@ class _PyPi(EnvConfig): EnvConfig.Config.env_prefix = "pypi_" - rss_feed_url = "https://pypi.org/rss/packages.xml" show_author_in_embed = False diff --git a/src/bot/exts/dragonfly/dragonfly.py b/src/bot/exts/dragonfly/dragonfly.py index 1862e3e..f6f98c5 100644 --- a/src/bot/exts/dragonfly/dragonfly.py +++ b/src/bot/exts/dragonfly/dragonfly.py @@ -72,7 +72,9 @@ def __init__(self, *, email_template: Template, package: PackageScanResult) -> N self.package = package async def on_submit(self, interaction: discord.Interaction): - content = self.email_template.render(package=self.package, description=self.description.value, rules=", ".join(self.package.matches)) + content = self.email_template.render( + package=self.package, description=self.description.value, rules=", ".join(self.package.matches) + ) log.info( "Sending report to with sender %s with recipient %s with bcc %s", diff --git a/src/bot/exts/pypi.py b/src/bot/exts/pypi.py index 80b4a4d..5085158 100644 --- a/src/bot/exts/pypi.py +++ b/src/bot/exts/pypi.py @@ -6,23 +6,22 @@ import discord from discord.ext import commands +from letsbuilda.pypi import PackageMetadata, PyPIServices from bot.bot import Bot from bot.constants import PyPiConfigs -from bot.pypi import get_packages -from bot.pypi.pypi import Package class EmbedPaginator: """Paginate embeds""" - def __init__(self, packages: list[Package], per_page: int) -> None: + def __init__(self, packages: list[PackageMetadata], per_page: int) -> None: self.idx = 0 self.per_page = per_page self.packages = packages self.embeds = self._build_embeds() - def _batched(self) -> Generator[list[Package], None, None]: + def _batched(self) -> Generator[list[PackageMetadata], None, None]: it = iter(self.packages) while True: batch = list(islice(it, self.per_page)) @@ -48,13 +47,13 @@ def _build_embeds(self) -> list[discord.Embed]: "", discord.utils.format_dt(package.publication_date), f"[Package Link]({package.package_link})", - f"[Inspector Link]({package.inspector_link})", + f"[Inspector Link](https://inspector.pypi.io/project/{package.title})", package.author if PyPiConfigs.show_author_in_embed and package.author else "", ) ), ) - embed.set_footer(text=f"Page {page_number+1}/{ceil(len(self.packages) / self.per_page)}") + embed.set_footer(text=f"Page {page_number + 1}/{ceil(len(self.packages) / self.per_page)}") embeds.append(embed) @@ -90,7 +89,7 @@ def last(self) -> None: class PackageViewer(discord.ui.View): """Package viewer""" - def __init__(self, *, packages: list[Package], author: discord.User | discord.Member) -> None: + def __init__(self, *, packages: list[PackageMetadata], author: discord.User | discord.Member) -> None: self.paginator = EmbedPaginator(packages, per_page=3) self.author = author self.message: discord.Message | None = None @@ -140,7 +139,8 @@ def __init__(self, bot: Bot): @commands.command() async def pypi(self, ctx: commands.Context) -> None: - packages = await get_packages(self.bot.http_session) + client = PyPIServices(http_session=self.bot.http_session) + packages = await client.get_rss_feed(client.NEWEST_PACKAGES_FEED_URL) view = PackageViewer(packages=packages, author=ctx.author) message = await ctx.send(embed=view.paginator.current, view=view) view.message = message diff --git a/src/bot/pypi/__init__.py b/src/bot/pypi/__init__.py deleted file mode 100644 index b737b50..0000000 --- a/src/bot/pypi/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Interactions with PyPI""" - -from .pypi import get_packages diff --git a/src/bot/pypi/pypi.py b/src/bot/pypi/pypi.py deleted file mode 100644 index 45b9a52..0000000 --- a/src/bot/pypi/pypi.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Interactions with PyPI""" - -from dataclasses import dataclass -from datetime import datetime -from xml.etree import ElementTree -from xml.etree.ElementTree import Element - -from aiohttp import ClientSession - -from bot.constants import PyPiConfigs - - -class PackageParserException(Exception): - """Raised when parsing fails""" - - -@dataclass -class Package: - """Package metadata""" - - title: str - package_link: str - inspector_link: str - guid: str - description: str | None - author: str | None - publication_date: datetime - - -def _parse_publication_date(publication_date: str) -> datetime: - return datetime.strptime(publication_date, "%a, %d %b %Y %H:%M:%S %Z") - - -def _find_item(element: Element, name: str) -> str: - item = element.find(name) - if item is None: - raise PackageParserException(f"<{name}> element was not found.") - if item.text is None: - raise PackageParserException(f"<{name}> element was found, but empty.") - - return item.text - - -def _parse_package(xml_element: Element) -> Package: - title = _find_item(xml_element, "title").split(" ")[0] - package_link = _find_item(xml_element, "link") - inspector_link = "https://inspector.pypi.io/project/" + title - guid = _find_item(xml_element, "guid") - publication_date = _find_item(xml_element, "pubDate") - publication_date = _parse_publication_date(publication_date) - description = description_tag.text if (description_tag := xml_element.find("description")) is not None else None - author = author_tag.text if (author_tag := xml_element.find("author")) is not None else None - - return Package( - title=title, - package_link=package_link, - guid=guid, - description=description, - author=author, - publication_date=publication_date, - inspector_link=inspector_link, - ) - - -async def get_packages(session: ClientSession) -> list[Package]: - """Get recent packages from PyPI""" - async with session.get(PyPiConfigs.rss_feed_url) as res: - text = await res.text() - root = ElementTree.fromstring(text) - - return [_parse_package(package) for package in root.iter("item")]