diff --git a/CHANGELOG.md b/CHANGELOG.md index b35073a8..7cdcbb19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Added support for _sending_ multiple HTTP header lines with the same name. ([#130](https://github.com/httpie/httpie/issues/130)) - Added support for _receiving_ multiple HTTP headers lines with the same name. ([#1207](https://github.com/httpie/httpie/issues/1207)) - Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/httpie/issues/1212)) +- Broken plugins will no longer crash the whole application. ([#1204](https://github.com/httpie/httpie/issues/1204)) ## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14) diff --git a/httpie/plugins/manager.py b/httpie/plugins/manager.py index 1b188e57..6d78653f 100644 --- a/httpie/plugins/manager.py +++ b/httpie/plugins/manager.py @@ -1,5 +1,6 @@ import sys import os +import warnings from itertools import groupby from operator import attrgetter @@ -69,9 +70,19 @@ class PluginManager(list): def load_installed_plugins(self, directory: Optional[Path] = None): for entry_point in self.iter_entry_points(directory): - plugin = entry_point.load() - plugin.package_name = get_dist_name(entry_point) - self.register(entry_point.load()) + plugin_name = get_dist_name(entry_point) + try: + plugin = entry_point.load() + except BaseException as exc: + warnings.warn( + f'While loading "{plugin_name}", an error ocurred: {exc}\n' + f'For uninstallations, please use either "httpie plugins uninstall {plugin_name}" ' + f'or "pip uninstall {plugin_name}" (depending on how you installed it in the first ' + 'place).' + ) + continue + plugin.package_name = plugin_name + self.register(plugin) # Auth def get_auth_plugins(self) -> List[Type[AuthPlugin]]: diff --git a/tests/conftest.py b/tests/conftest.py index fa0b367a..5e8c5110 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ from pytest_httpbin import certs from .utils import HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN, HTTPBIN_WITH_CHUNKED_SUPPORT from .utils.plugins_cli import ( # noqa + broken_plugin, dummy_plugin, dummy_plugins, httpie_plugins, diff --git a/tests/test_plugins_cli.py b/tests/test_plugins_cli.py index 39ee9c08..d644aba7 100644 --- a/tests/test_plugins_cli.py +++ b/tests/test_plugins_cli.py @@ -93,6 +93,28 @@ def test_plugins_double_uninstall(httpie_plugins, httpie_plugins_success, dummy_ ) +def test_broken_plugins(httpie_plugins, httpie_plugins_success, dummy_plugin, broken_plugin): + httpie_plugins_success("install", dummy_plugin.path, broken_plugin.path) + + with pytest.warns( + UserWarning, + match=( + f'While loading "{broken_plugin.name}", an error' + ' ocurred: broken plugin' + ) + ): + data = parse_listing(httpie_plugins_success('list')) + assert len(data) == 2 + + # We load before the uninstallation, so it will warn again. + with pytest.warns(UserWarning): + httpie_plugins_success("uninstall", broken_plugin.name) + + # No warning now, since it is uninstalled. + data = parse_listing(httpie_plugins_success('list')) + assert len(data) == 1 + + def test_plugins_cli_error_message_without_args(): # No arguments result = httpie(no_debug=True) diff --git a/tests/utils/plugins_cli.py b/tests/utils/plugins_cli.py index 4e4d8a07..a750e664 100644 --- a/tests/utils/plugins_cli.py +++ b/tests/utils/plugins_cli.py @@ -178,6 +178,14 @@ def dummy_plugin(interface): return interface.make_dummy_plugin() +@pytest.fixture(scope='function') +def broken_plugin(interface): + base_plugin = interface.make_dummy_plugin() + with open(base_plugin.path / (base_plugin.import_name + '.py'), 'a') as stream: + stream.write('raise ValueError("broken plugin")\n') + return base_plugin + + @pytest.fixture(scope='function') def dummy_plugins(interface): # Multiple plugins with different configurations