From 579d118a67cb37fb390174280de58c83aee2da34 Mon Sep 17 00:00:00 2001 From: dgw Date: Sat, 29 Aug 2020 08:11:00 -0500 Subject: [PATCH 1/2] Messy package search implementation Never planned to support this feature, so a refactor is probably due. However, this slightly-kludgy approach doesn't seem to break anything. Also (re)discovered a bug in Sopel while writing this patch, which is nice since it'll push us toward making plugin loading more robust. --- sopel_pypi/__init__.py | 50 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/sopel_pypi/__init__.py b/sopel_pypi/__init__.py index fa2fe52..d64a373 100644 --- a/sopel_pypi/__init__.py +++ b/sopel_pypi/__init__.py @@ -6,6 +6,8 @@ from __future__ import unicode_literals, absolute_import, division, print_function from datetime import datetime +import enum +import xmlrpc.client import requests @@ -23,6 +25,11 @@ class NoSuchVersionError(PyPIError): pass +class ReturnCode(enum.Enum): + FAILED = enum.auto() + NOT_FOUND = enum.auto() + + def get_pypi_info(package, version): """Get the package info from PyPI, and handle network errors.""" try: @@ -79,18 +86,24 @@ def format_pypi_info(data): ) +def search_pypi(query): + """Query PyPI's XML-RPC API for relevant packages.""" + # Making the XML-RPC client a global breaks Sopel's plugin loading; + # see https://github.com/sopel-irc/sopel/issues/1931 + # Using bot.memory would require an extra `bot` param here. + # Just take the slight perf hit of creating the object every time. + pypi = xmlrpc.client.ServerProxy('https://pypi.org/pypi') + return pypi.search({'name': query, 'summary': query}, 'or') + + def say_info(bot, package, version, commanded=False): """Fetch, format, and output the package info to IRC.""" try: data = get_pypi_info(package, version) except NoSuchVersionError as e: - if commanded: - bot.say(e.args[0]) - return + return ReturnCode.NOT_FOUND, e.args[0] except PyPIError: - if commanded: - bot.say("Sorry, there was an error accessing PyPI. Please try again later.") - return + return ReturnCode.FAILED, "Sorry, there was an error accessing PyPI. Please try again later." message = format_pypi_info(data) if commanded: @@ -117,4 +130,27 @@ def pypi_command(bot, trigger): package = trigger.group(3) version = trigger.group(4) - say_info(bot, package, version, commanded=True) + if not trigger.group(5): + # If trigger group 5 exists, the user's input was too long for a + # standard package query with optional version, and we shouldn't + # even try to do anything but search. + ret, msg = say_info(bot, package, version, commanded=True) + + if ret is None: + return # successful output + + if ret == ReturnCode.FAILED: + # no reason to fall back on search if PyPI appears to be down + bot.say(msg) + return + + if version and ret == ReturnCode.NOT_FOUND and all(c in '1234567890.' for c in version): + # Don't fall back on search for NOT_FOUND with a version number + bot.say(msg) + return + + # Fall back on search if the direct lookup returned no results + hits = search_pypi(trigger.group(2)) + if hits: + package = hits[0] + say_info(bot, package['name'], package['version'], commanded=True) From f54ab353220449939fbcfb0d63b6d09334e18558 Mon Sep 17 00:00:00 2001 From: dgw Date: Fri, 27 Nov 2020 16:06:44 -0600 Subject: [PATCH 2/2] Add Python version metadata for enum support Search implementation uses enums, which were added in Python 3.4 -- and more specifically, a new Python 3.6 feature, enum.auto. --- setup.cfg | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.cfg b/setup.cfg index 0e71983..cdb9b81 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,6 +11,11 @@ classifiers = Intended Audience :: System Administrators License :: Eiffel Forum License (EFL) License :: OSI Approved :: Eiffel Forum License + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 Topic :: Communications :: Chat :: Internet Relay Chat [options] @@ -19,6 +24,7 @@ zip_safe = false include_package_data = true install_requires = sopel>=7.0 +python_requires = >= 3.6 [options.entry_points] sopel.plugins =