Source code for univention.udm.plugins
# SPDX-FileCopyrightText: 2018-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
from __future__ import annotations
import importlib
import os.path
from glob import glob
from typing import TYPE_CHECKING, Any, cast
if TYPE_CHECKING:
from collections.abc import Iterator
[docs]
class Plugin(type):
"""Meta class for plugins."""
def __new__(mcs: type[Plugin], name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> Plugin:
new_cls = cast(Plugin, super().__new__(mcs, name, bases, attrs))
Plugins.add_plugin(new_cls)
return new_cls
[docs]
class Plugins:
"""Register `Plugin` subclasses and iterate over them."""
_plugins: list[Plugin] = []
_imported: dict[str, bool] = {}
def __init__(self, python_path: str) -> None:
"""
:param str python_path: fully dotted Python path that the plugins will
be found below
"""
self.python_path = python_path
self._imported.setdefault(python_path, False)
[docs]
@classmethod
def add_plugin(cls, plugin: Plugin) -> None:
"""
Called by `Plugin` meta class to register a new `Plugin` subclass.
:param type plugin: a `Plugin` subclass
"""
cls._plugins.append(plugin)
def __iter__(self) -> Iterator[Plugin]:
"""
Iterator for registered `Plugin` subclasses.
:return: `Plugin` subclass
"""
self.load()
for plugin in self._plugins:
if plugin.__module__.startswith(self.python_path):
yield plugin
[docs]
def load(self) -> None:
"""Load plugins."""
if self._imported.get(self.python_path):
return
base_module = importlib.import_module(self.python_path)
assert base_module.__file__
base_module_dir = os.path.dirname(base_module.__file__)
path = os.path.join(base_module_dir, '*.py')
for pymodule in glob(path):
pymodule_name = os.path.basename(pymodule)[:-3] # without .py
importlib.import_module(f'{self.python_path}.{pymodule_name}')
self._imported[self.python_path] = True