Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: plugin enable/disable/enable #1188

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Loading