Skip to content

Commit

Permalink
fix: plugin enable/disable/enable
Browse files Browse the repository at this point in the history
If we enable, then disable, then enable a plugin again within the same
call to Tutor, then the plugin module is not imported properly the
second time. This is because it remains in the import cache. We
discovered this while implementing a long-running web app for Tutor.
  • Loading branch information
regisb committed Jan 9, 2025
1 parent 9841287 commit 5f1b3d9
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog.d/20250109_184104_regis_plugin_unload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- [Bugfix] Properly reload a plugin module on enable/disable/enable. This is an edge case that should not have affected anyone. (by @regisb)
30 changes: 29 additions & 1 deletion tutor/plugins/v1.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import importlib.util
import os
from glob import glob
import sys

import importlib_metadata

from tutor import hooks
from tutor.types import Config

from .base import PLUGINS_ROOT

Expand Down Expand Up @@ -71,8 +73,34 @@ def discover_package(entrypoint: importlib_metadata.EntryPoint) -> None:
dist_version = entrypoint.dist.version if entrypoint.dist else "Unknown"
hooks.Filters.PLUGINS_INFO.add_item((name, dist_version))

# Import module on enable
@hooks.Actions.PLUGIN_LOADED.add()
def load(plugin_name: str) -> None:
"""
Import module on enable.
"""
if name == plugin_name:
importlib.import_module(entrypoint.value)

# Remove module from cache on disable
@hooks.Actions.PLUGIN_UNLOADED.add()
def unload(plugin_name: str, _root: str, _config: Config) -> None:
"""
Remove plugin module from import cache on disable.
This is necessary in one particular use case: when a plugin is enabled,
disabled, and enabled again -- all within the same call to Tutor. In such a
case, the following happens:
1. plugin enabled: the plugin module is imported. It is automatically added by
Python to the import cache.
2. plugin disabled: action and filter callbacks are removed, but the module
remains in the import cache.
3. plugin enabled again: the plugin module is imported. But because it's in the
import cache, the module instructions are not executed again.
This is not supposed to happen when we run Tutor normally from the CLI. But when
running a long-lived process, such as a web app, where a plugin might be enabled
and disabled multiple times, this becomes an issue.
"""
if name == plugin_name and entrypoint.value in sys.modules:
sys.modules.pop(entrypoint.value)

0 comments on commit 5f1b3d9

Please sign in to comment.