Skip to content

Commit

Permalink
feat: users can now create custom plugins in their configuration dir
Browse files Browse the repository at this point in the history
  • Loading branch information
jtpavlock committed Oct 9, 2022
1 parent 7d450b2 commit 84347f6
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 10 deletions.
5 changes: 4 additions & 1 deletion docs/getting_started.rst
Expand Up @@ -64,7 +64,10 @@ Most configuration options reside in their relevant plugin, however there are th
Overrides the list of default plugins.

``disable_plugins = []``
List of any plugins to explicitly disable.
List of any plugins to explicitly disable. This takes priority over ``enable_plugins``.

``enable_plugins = []``
List of any plugins to explicitly enable.

.. _library_path config option:

Expand Down
31 changes: 23 additions & 8 deletions moe/config.py
Expand Up @@ -14,6 +14,7 @@
import logging
import os
import re
import sys
from pathlib import Path
from types import ModuleType
from typing import NamedTuple, Optional, Union, cast
Expand Down Expand Up @@ -182,6 +183,7 @@ def add_config_validator(settings: dynaconf.base.LazySettings):
validators = [
dynaconf.Validator("DEFAULT_PLUGINS", default=DEFAULT_PLUGINS),
dynaconf.Validator("DISABLE_PLUGINS", default=set()),
dynaconf.Validator("ENABLE_PLUGINS", default=set()),
dynaconf.Validator("LIBRARY_PATH", default="~/Music"),
dynaconf.Validator("ORIGINAL_DATE", default=False),
]
Expand Down Expand Up @@ -360,9 +362,9 @@ def _setup_plugins(self, core_plugins: dict[str, str] = CORE_PLUGINS):
self.pm.hook.add_config_validator(settings=self.settings)
self._validate_settings()

config_plugins = set(self.settings.default_plugins) - set(
self.settings.disable_plugins
)
config_plugins = (
set(self.settings.default_plugins) | set(self.settings.enable_plugins)
) - set(self.settings.disable_plugins)

# the 'import' plugin maps to the 'moe_import' package
if "import" in config_plugins:
Expand All @@ -373,7 +375,13 @@ def _setup_plugins(self, core_plugins: dict[str, str] = CORE_PLUGINS):
self.pm.register(importlib.import_module("moe.cli"), name="cli")

# register plugin hookimpls for all enabled plugins
self._register_internal_plugins(config_plugins)
internal_plugin_dir = Path(__file__).resolve().parent / "plugins"
self._register_local_plugins(
config_plugins, internal_plugin_dir, "moe.plugins."
)
if Path(self.config_dir / "plugins").exists():
sys.path.append(str(self.config_dir / "plugins"))
self._register_local_plugins(config_plugins, self.config_dir / "plugins")

# register plugin hookimpls for all extra plugins
for extra_plugin in self._extra_plugins:
Expand All @@ -387,13 +395,20 @@ def _setup_plugins(self, core_plugins: dict[str, str] = CORE_PLUGINS):

log.debug(f"Registered plugins. [plugins={self.pm.list_name_plugin()}]")

def _register_internal_plugins(self, enabled_plugins):
"""Registers all internal plugins in `enabled_plugins`."""
plugin_dir = Path(__file__).resolve().parent / "plugins"
def _register_local_plugins(
self, enabled_plugins: set[str], plugin_dir: Path, pkg_name: str = ""
):
"""Registers all internal plugins in `enabled_plugins`.
Args:
enabled_plugins: All enabled plugins as specified by the config.
plugin_dir: Directory of plugins to register.
pkg_name: Optional common package name the plugins belong to.
Include the trailing '.'.
"""
for plugin_path in plugin_dir.iterdir():
plugin_name = plugin_path.stem

if plugin_path.stem in enabled_plugins:
plugin = importlib.import_module("moe.plugins." + plugin_name)
plugin = importlib.import_module(pkg_name + plugin_name)
self.pm.register(plugin, plugin_name)
6 changes: 5 additions & 1 deletion tests/conftest.py
Expand Up @@ -76,6 +76,7 @@ def tmp_config(
tmp_db: Whether or not to use a temporary (in-memory) database. If ``True``,
the database will be initialized regardless of ``init_db``.
extra_plugins: Any additional plugins to enable.
config_dir: Optionally specifiy a config directory to use.
Yields:
The configuration instance.
Expand All @@ -86,8 +87,11 @@ def _tmp_config(
init_db: bool = False,
tmp_db: bool = False,
extra_plugins: Optional[list[ExtraPlugin]] = None,
config_dir: Optional[Path] = None,
) -> Config:
config_dir = tmp_path_factory.mktemp("config")
config_dir = config_dir or tmp_path_factory.mktemp("config")
assert config_dir

if "library_path" not in settings:
settings += f"\nlibrary_path = '{LIBRARY_PATH.resolve()}'"

Expand Down
23 changes: 23 additions & 0 deletions tests/test_config.py
@@ -1,5 +1,6 @@
"""Tests configuration."""

import shutil
from pathlib import Path
from unittest.mock import patch

Expand Down Expand Up @@ -71,12 +72,34 @@ def test_config_plugins(self, tmp_config):
for plugin in plugins:
assert config.CONFIG.pm.has_plugin(plugin)

def test_enable_plugins(self, tmp_config):
"""We can explictly enable plugins."""
tmp_config(
settings="""default_plugins = ["cli"]
enable_plugins = ["list"]"""
)

assert config.CONFIG.pm.has_plugin("list")

def test_extra_plugins(self, tmp_config):
"""Any given additional plugins are also registered."""
tmp_config(extra_plugins=[ExtraPlugin(TestPlugins, "config_plugin")])

assert config.CONFIG.pm.has_plugin("config_plugin")

def test_register_local_user_plugins(self, tmp_config, tmp_path_factory):
"""We can register plugins in the user plugin directory."""
config_dir = tmp_path_factory.mktemp("config")
plugin_dir = config_dir / "plugins"
plugin_dir.mkdir()

list_path = Path(__file__).resolve().parent.parent / "moe/plugins/list.py"
shutil.copyfile(list_path, plugin_dir / "my_list.py")

tmp_config(settings="enable_plugins = ['my_list']", config_dir=config_dir)

assert config.CONFIG.pm.has_plugin("my_list")


class ConfigPlugin:
"""Plugin that implements the config hooks for testing."""
Expand Down

0 comments on commit 84347f6

Please sign in to comment.