Skip to content

Commit eb78099

Browse files
sea-odoobrinkflew
authored andcommitted
[IMP] Relocating plugin files to the configuration directory (#120)
* [IMP] Relocating plugin files to the configuration directory * [REF] Switch folder to python package (move out plugins of odev path) * [FIX] Attempt to fix "module not found error" * [REF] Add importlib and attempt to import plugin modules during loading. * [REM] Clean useless import mechanism * [REF] Replace dynamic plugin loading with explicit `__all__` exports in common modules. * [FIX] Clean dynamic import code * [FIX] Import lib is still needed * [FIX] Check ipdb in the new plugin folder * [IMP] Bump version / Move upgrade script * [IMP] Add back contextlib
1 parent 27fb476 commit eb78099

File tree

11 files changed

+106
-119
lines changed

11 files changed

+106
-119
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ htmlcov
1818
coverage.xml
1919

2020
# --- Odev plugins, loaded as submodules
21-
odev/plugins/*
2221
tests/plugins/*
2322

2423
# --- Temporary and testing files

install.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ if [ $? -ne 0 ]; then
3434
exit 1
3535
fi
3636

37+
echo "Creating plugins directory"
38+
mkdir -p ~/.config/odev/plugins
39+
3740
echo "Installing dependencies"
3841
~/.config/odev/venv/bin/pip install -r requirements.txt > /dev/null 2>&1
3942
~/.config/odev/venv/bin/pip install -r requirements-dev.txt > /dev/null 2>&1
4043

41-
find odev/plugins/*/ -type f -name 'requirements.txt' | while read reqfile; do
44+
find ~/.config/odev/plugins/*/ -type f -name 'requirements.txt' | while read reqfile; do
4245
~/.config/odev/venv/bin/pip install -r "$reqfile" > /dev/null 2>&1
4346
if [ $? -ne 0 ]; then
4447
echo "Failed to install dependencies from $reqfile"

odev/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222
# or merged change.
2323
# ------------------------------------------------------------------------------
2424

25-
__version__ = "4.23.0"
25+
__version__ = "4.24.0"

odev/common/__init__.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,7 @@
11
"""Common utilities for odev."""
22

3-
import pkgutil
4-
from importlib import import_module
5-
from pathlib import Path
6-
from typing import cast
7-
8-
9-
# --- Import helper submodules -------------------------------------------------
10-
11-
from . import actions
123
from . import arguments as args
13-
from . import bash
14-
from . import config
15-
from . import console
16-
from . import debug
17-
from . import logging
184
from . import odev
19-
from . import odoobin
20-
from . import postgres
21-
from . import progress
22-
from . import python
23-
from . import signal_handling
24-
from . import string
25-
from . import thread
26-
from . import version
27-
28-
29-
# --- Load plugins' helpers ----------------------------------------------------
30-
31-
odev_path = Path(__file__).parents[1]
32-
33-
plugins = [path for path in (odev_path / "plugins").glob("*/common") if path.is_dir()]
34-
modules = pkgutil.iter_modules([directory.as_posix() for directory in plugins])
35-
36-
for module_info in modules:
37-
module_path = cast(str, module_info.module_finder.path).replace(str(odev_path.parent) + "/", "").replace("/", ".") # type: ignore [union-attr]
38-
module = import_module(f"{module_path}.{module_info.name}")
39-
405

416
# --- Setup the framework and make it globally available -----------------------
427

odev/common/commands/__init__.py

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
"""Odev base command classes."""
22

3-
import pkgutil
4-
from importlib import import_module
5-
from pathlib import Path
6-
from inspect import isclass
7-
8-
# --- Common modules -----------------------------------------------------------
9-
from typing import TypeVar, cast
3+
from typing import TypeVar
104

115
from .base import Command, CommandError
126
from .database import DatabaseCommand, DatabaseOrRepositoryCommand, LocalDatabaseCommand, RemoteDatabaseCommand
@@ -19,23 +13,20 @@
1913
OdoobinTemplateCommand,
2014
)
2115

22-
23-
# --- Plugins ------------------------------------------------------------------
24-
25-
odev_path = Path(__file__).parent.parent.parent
26-
27-
plugins = [path for path in (odev_path / "plugins").glob("*/common/commands") if path.is_dir()]
28-
modules = pkgutil.iter_modules([directory.as_posix() for directory in plugins])
29-
30-
for module_info in modules:
31-
module_path = cast(str, module_info.module_finder.path).replace(str(odev_path.parent) + "/", "").replace("/", ".") # type: ignore [union-attr]
32-
module = import_module(f"{module_path}.{module_info.name}")
33-
34-
for attribute in dir(module):
35-
obj = getattr(module, attribute)
36-
37-
if isclass(obj) and issubclass(obj, Command) and obj is not Command:
38-
globals()[attribute] = obj
39-
40-
4116
CommandType = TypeVar("CommandType", Command, DatabaseCommand)
17+
18+
__all__ = [
19+
"TEMPLATE_SUFFIX",
20+
"Command",
21+
"CommandError",
22+
"CommandType",
23+
"DatabaseCommand",
24+
"DatabaseOrRepositoryCommand",
25+
"GitCommand",
26+
"LocalDatabaseCommand",
27+
"OdoobinCommand",
28+
"OdoobinShellCommand",
29+
"OdoobinShellScriptCommand",
30+
"OdoobinTemplateCommand",
31+
"RemoteDatabaseCommand",
32+
]

odev/common/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,8 @@ def __init_sections(self):
293293

294294
modules = [inspect.getmodule(self)]
295295

296-
# Find all odev/plugins/*/config.py files and import their Section subclasses
297-
plugins_config_paths = glob.glob(str(Path(__file__).parent.parent / "plugins" / "*" / "config.py"))
296+
# Find all ~/.config/odev/plugins/*/config.py files and import their Section subclasses
297+
plugins_config_paths = glob.glob(str(CONFIG_DIR / "plugins" / "*" / "config.py"))
298298

299299
for config_path in plugins_config_paths:
300300
module_name = f"odev.plugins.{Path(config_path).parent.name}.config"

odev/common/connectors/__init__.py

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,17 @@
11
"""Connectors to external services."""
22

3-
import pkgutil
4-
from importlib import import_module
5-
from pathlib import Path
6-
from typing import cast
7-
from inspect import isclass
8-
9-
# --- Common modules -----------------------------------------------------------
103
from .base import Connector
114
from .git import GitConnector, GitWorktree, Stash
125
from .postgres import PostgresConnector
136
from .rest import RestConnector
147
from .rpc import RpcConnector
158

16-
17-
# --- Plugins ------------------------------------------------------------------
18-
19-
odev_path = Path(__file__).parent.parent.parent
20-
21-
plugins = [path for path in (odev_path / "plugins").glob("*/common/connectors") if path.is_dir()]
22-
modules = pkgutil.iter_modules([directory.as_posix() for directory in plugins])
23-
24-
for module_info in modules:
25-
module_path = cast(str, module_info.module_finder.path).replace(str(odev_path.parent) + "/", "").replace("/", ".") # type: ignore [union-attr]
26-
module = import_module(f"{module_path}.{module_info.name}")
27-
28-
for attribute in dir(module):
29-
obj = getattr(module, attribute)
30-
31-
if isclass(obj) and issubclass(obj, Connector) and obj is not Connector:
32-
globals()[attribute] = obj
9+
__all__ = [
10+
"Connector",
11+
"GitConnector",
12+
"GitWorktree",
13+
"PostgresConnector",
14+
"RestConnector",
15+
"RpcConnector",
16+
"Stash",
17+
]

odev/common/databases/__init__.py

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,15 @@
11
"""Database handling."""
22

3-
import pkgutil
4-
from importlib import import_module
5-
from pathlib import Path
6-
from typing import cast
7-
from inspect import isclass
8-
9-
# --- Common modules -----------------------------------------------------------
103
from .base import Branch, Database, DummyDatabase, Filestore, Repository
114
from .local import LocalDatabase
125
from .remote import RemoteDatabase
136

14-
15-
# --- Plugins ------------------------------------------------------------------
16-
17-
odev_path = Path(__file__).parent.parent.parent
18-
19-
plugins = [path for path in (odev_path / "plugins").glob("*/common/databases") if path.is_dir()]
20-
modules = pkgutil.iter_modules([directory.as_posix() for directory in plugins])
21-
22-
for module_info in modules:
23-
module_path = cast(str, module_info.module_finder.path).replace(str(odev_path.parent) + "/", "").replace("/", ".") # type: ignore [union-attr]
24-
module = import_module(f"{module_path}.{module_info.name}")
25-
26-
for attribute in dir(module):
27-
obj = getattr(module, attribute)
28-
29-
if isclass(obj) and issubclass(obj, Database) and obj is not Database:
30-
globals()[attribute] = obj
7+
__all__ = [
8+
"Branch",
9+
"Database",
10+
"DummyDatabase",
11+
"Filestore",
12+
"LocalDatabase",
13+
"RemoteDatabase",
14+
"Repository",
15+
]

odev/common/debug.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pathlib import Path
77

88
from odev.common import bash, string
9+
from odev.common.config import CONFIG_DIR
910
from odev.common.logging import logging
1011

1112

@@ -41,7 +42,13 @@ def find_debuggers(root: str | Path) -> Generator[tuple[Path, int], None, None]:
4142

4243
# ------------------------------------------------------------------------------
4344
# Find calls to interactive debuggers within odev's source code
44-
debuggers = [f"{file.as_posix()}:{line}" for file, line in find_debuggers(Path(__file__).parents[1])]
45+
sources = [Path(__file__).parents[1]]
46+
plugins_path = CONFIG_DIR / "plugins"
47+
48+
if plugins_path.is_dir():
49+
sources.append(plugins_path)
50+
51+
debuggers = [f"{file.as_posix()}:{line}" for source in sources for file, line in find_debuggers(source)]
4552

4653
if debuggers:
4754
logger.warning(f"Interactive debuggers detected:\n{string.join_bullet(debuggers)}")

odev/common/odev.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Self update Odev by pulling latest changes from the git repository."""
22

33
import contextlib
4+
import importlib
45
import inspect
56
import os
67
import pkgutil
@@ -36,7 +37,7 @@
3637
from odev.common import progress, string
3738
from odev.common.commands import CommandType
3839
from odev.common.commands.database import DatabaseType
39-
from odev.common.config import Config
40+
from odev.common.config import CONFIG_DIR, Config
4041
from odev.common.connectors.git import GitConnector, Stash
4142
from odev.common.console import Console, console
4243
from odev.common.errors import OdevError
@@ -184,7 +185,7 @@ def tests_path(self) -> Path:
184185
@property
185186
def plugins_path(self) -> Path:
186187
"""Local path to the plugins directory."""
187-
return self.base_path / "plugins"
188+
return CONFIG_DIR / "plugins"
188189

189190
@property
190191
def commands_path(self) -> Path:
@@ -516,8 +517,12 @@ def import_commands(self, sources: Iterable[Path]) -> list[type[CommandType]]:
516517
if not isinstance(module_info.module_finder, FileFinder):
517518
raise TypeError("Module finder is not a FileFinder instance")
518519

519-
module_path = Path(module_info.module_finder.path) / f"{module_info.name}.py"
520-
spec = spec_from_file_location(module_path.stem, module_path.as_posix())
520+
if module_info.ispkg:
521+
module_path = Path(module_info.module_finder.path) / module_info.name / "__init__.py"
522+
else:
523+
module_path = Path(module_info.module_finder.path) / f"{module_info.name}.py"
524+
525+
spec = spec_from_file_location(module_info.name, module_path.as_posix())
521526

522527
if spec is None or spec.loader is None:
523528
raise ImportError(f"Cannot load module {module_info.name} from {module_path.as_posix()}")
@@ -566,11 +571,29 @@ def register_plugin_commands(self) -> None:
566571

567572
def _register_plugin_commands(self) -> None:
568573
"""Register all commands from the plugins directories."""
574+
# Ensure odev.plugins exists as a module so legacy imports work
575+
odev_module = sys.modules.get("odev")
576+
if odev_module:
577+
if not hasattr(odev_module, "plugins"):
578+
odev_module.plugins = ModuleType("odev.plugins")
579+
odev_module.plugins.__path__ = []
580+
sys.modules["odev.plugins"] = odev_module.plugins
581+
582+
if str(self.plugins_path) not in odev_module.plugins.__path__:
583+
odev_module.plugins.__path__.append(str(self.plugins_path))
584+
569585
for plugin in self.plugins:
570586
logger.debug(
571587
f"Loading plugin {plugin.name!r} version {string.stylize(plugin.manifest['version'], 'repr.version')}"
572588
)
573589

590+
self._install_plugin_requirements(plugin.path)
591+
592+
try:
593+
importlib.import_module(f"odev.plugins.{plugin.path.name}")
594+
except ImportError as error:
595+
logger.debug(f"Could not import plugin module {plugin.path.name}: {error}")
596+
574597
for command_class in self.import_commands(plugin.path.glob("commands/**")):
575598
command_names = [command_class._name] + (list(command_class._aliases) or [])
576599
base_command_class = self.commands.get(command_class._name)

0 commit comments

Comments
 (0)