Skip to content

Commit cb1d5de

Browse files
committed
fix: plugin enable/disable/enable
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.
1 parent 40579db commit cb1d5de

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- [Bugfix] Properly reload a plugin module on enable/disable/enable. This is an edge case that should not have affected anyone. (by @regisb)

tutor/plugins/v1.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import importlib.util
22
import os
33
from glob import glob
4+
import sys
45

56
import importlib_metadata
67

78
from tutor import hooks
9+
from tutor.types import Config
810

911
from .base import PLUGINS_ROOT
1012

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

74-
# Import module on enable
7576
@hooks.Actions.PLUGIN_LOADED.add()
7677
def load(plugin_name: str) -> None:
78+
"""
79+
Import module on enable.
80+
"""
7781
if name == plugin_name:
7882
importlib.import_module(entrypoint.value)
83+
84+
# Remove module from cache on disable
85+
@hooks.Actions.PLUGIN_UNLOADED.add()
86+
def unload(plugin_name: str, _root: str, _config: Config) -> None:
87+
"""
88+
Remove plugin module from import cache on disable.
89+
90+
This is necessary in one particular use case: when a plugin is enabled,
91+
disabled, and enabled again -- all within the same call to Tutor. In such a
92+
case, the following happens:
93+
94+
1. plugin enabled: the plugin module is imported. It is automatically added by
95+
Python to the import cache.
96+
2. plugin disabled: action and filter callbacks are removed, but the module
97+
remains in the import cache.
98+
3. plugin enabled again: the plugin module is imported. But because it's in the
99+
import cache, the module instructions are not executed again.
100+
101+
This is not supposed to happen when we run Tutor normally from the CLI. But when
102+
running a long-lived process, such as a web app, where a plugin might be enabled
103+
and disabled multiple times, this becomes an issue.
104+
"""
105+
if name == plugin_name and entrypoint.value in sys.modules:
106+
sys.modules.pop(entrypoint.value)

0 commit comments

Comments
 (0)