|
1 | 1 | import importlib.util
|
2 | 2 | import os
|
3 | 3 | from glob import glob
|
| 4 | +import sys |
4 | 5 |
|
5 | 6 | import importlib_metadata
|
6 | 7 |
|
7 | 8 | from tutor import hooks
|
| 9 | +from tutor.types import Config |
8 | 10 |
|
9 | 11 | from .base import PLUGINS_ROOT
|
10 | 12 |
|
@@ -71,8 +73,34 @@ def discover_package(entrypoint: importlib_metadata.EntryPoint) -> None:
|
71 | 73 | dist_version = entrypoint.dist.version if entrypoint.dist else "Unknown"
|
72 | 74 | hooks.Filters.PLUGINS_INFO.add_item((name, dist_version))
|
73 | 75 |
|
74 |
| - # Import module on enable |
75 | 76 | @hooks.Actions.PLUGIN_LOADED.add()
|
76 | 77 | def load(plugin_name: str) -> None:
|
| 78 | + """ |
| 79 | + Import module on enable. |
| 80 | + """ |
77 | 81 | if name == plugin_name:
|
78 | 82 | 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