Skip to content

Commit

Permalink
Improve plugin error handling
Browse files Browse the repository at this point in the history
- Add mechanism to show plugin loading errors.
- Disable pluggy hooks for docs & unit tests
  • Loading branch information
lowell80 committed Oct 5, 2023
1 parent 106b193 commit 5c7d17b
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 11 deletions.
1 change: 1 addition & 0 deletions ksconf/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class _UNSET:
# Environmental error
EXIT_CODE_ENV_BUSTED = 120
EXIT_CODE_BAD_PY_VERSION = 121
EXIT_CODE_BROKEN_PLUGIN = 122


# This gets properly supported in Python 3.6, but until then....
Expand Down
32 changes: 28 additions & 4 deletions ksconf/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@

from pluggy import HookimplMarker, PluginManager

from ksconf.consts import PLUGGY_DISABLE_HOOK, PLUGGY_HOOK, is_debug
from ksconf.consts import EXIT_CODE_BROKEN_PLUGIN, PLUGGY_DISABLE_HOOK, PLUGGY_HOOK, is_debug
from ksconf.hookspec import KsconfHookSpecs

# decorator to mark ksconf hooks
ksconf_hook = HookimplMarker(PLUGGY_HOOK)


class BadPluginWarning(UserWarning):
""" Issue with one or more plugins """
pass


class _plugin_manager:
"""
Lazy loading PluginManager proxy that allows simple module-level access to
Expand All @@ -27,7 +32,7 @@ def log(self, message):

def _startup(self):
# This runs on demand, exactly once.
pm = PluginManager(PLUGGY_HOOK)
self.__plugin_manager = pm = PluginManager(PLUGGY_HOOK)

if PLUGGY_DISABLE_HOOK in os.environ:
disabled_hooks = os.environ[PLUGGY_DISABLE_HOOK].split()
Expand All @@ -36,8 +41,27 @@ def _startup(self):
pm.set_blocked(hook)

pm.add_hookspecs(KsconfHookSpecs)
pm.load_setuptools_entrypoints("ksconf_plugin")
self.__plugin_manager = pm

# Disable *ALL* 'ksconf_plugin' hooks
disabled_things = os.environ.get("KSCONF_DISABLE_PLUGINS", "").split()
self.log(f"Disabled stuff: {disabled_things }")
if "ksconf_plugin" in disabled_things:
if is_debug:
self.log("All plugins are disabled")
return

# XXX: Find a way to report *which* plugin failed. For now, use traceback
try:
pm.load_setuptools_entrypoints("ksconf_plugin")
except ModuleNotFoundError as e:
self.log("Unable to load one or more ksconf plugins. "
f"Please correct or uninstall the offending plugin.\n {e}\n"
"Run with KSCONF_DEBUG=1 to see a traceback")
if is_debug():
raise e
else:
sys.exit(EXIT_CODE_BROKEN_PLUGIN)

# Maybe this should be it's own setting someday? (disconnected from KSCONF_DEBUG)
if is_debug():
self.enable_monitoring()
Expand Down
20 changes: 15 additions & 5 deletions ksconf/setup_entrypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
class Ep(NamedTuple):
name: str
module_name: str
object_name: str
object_name: str = None

@property
def formatted(self):
if self.object_name is None:
return f"{self.name} = {self.module_name}"
else:
return f"{self.name} = {self.module_name}:{self.object_name}"


# autopep8: off
Expand Down Expand Up @@ -49,7 +56,7 @@ def get_entrypoints_setup():
""" Build entry point text descriptions for ksconf packaging """
setup = {}
for (group, entries) in _entry_points.items():
setup[group] = [f"{ep.name} = {ep.module_name}:{ep.object_name}" for ep in entries]
setup[group] = [ep.formatted for ep in entries]
return setup


Expand Down Expand Up @@ -78,10 +85,13 @@ def get_entrypoints_fallback(group):
def debug():
# For debugging internally defined entrypoints
print("Builtin entrypoints:")
for (_, entries) in _entry_points.items():
print("[group]")
for (group, entries) in _entry_points.items():
print(f"[{group}]")
for ep in entries:
print(f"{ep.name:15} = {ep.module_name:30} : {ep.object_name}")
if ep.object_name:
print(f"{ep.name:15} = {ep.module_name:30} : {ep.object_name}")
else:
print(f"{ep.name:15} = {ep.module_name:30}")
print("")


Expand Down
2 changes: 1 addition & 1 deletion make_dyn_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"PYTHONWARNINGS": "ignore",
"PYTHONIOENCODING": "utf-8",
"PYTHONHASHSEED": "1",
"KSCONF_DISABLE_PLUGINS": "ksconf_cmd",
"KSCONF_DISABLE_PLUGINS": "ksconf_cmd ksconf_plugin",
}


Expand Down
2 changes: 1 addition & 1 deletion run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
del os.environ[k]

# Tell KSCONF cmd loaders to use the locally defined list, and ignore externally defined commands
os.environ["KSCONF_DISABLE_PLUGINS"] = "ksconf_cmd"
os.environ["KSCONF_DISABLE_PLUGINS"] = "ksconf_cmd ksconf_plugin"


def run_all():
Expand Down

0 comments on commit 5c7d17b

Please sign in to comment.