Source code for univention.portal

#!/usr/bin/python3
#
# Univention Portal
#
# SPDX-FileCopyrightText: 2020-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
#

import importlib
import os.path
from collections.abc import Iterator
from glob import glob


[docs] class Plugin(type): """Meta class for plugins.""" def __new__(mcs, name, bases, attrs): new_cls = 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 :rtype: type """ 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 sorted(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
[docs] def get_all_dynamic_classes() -> Iterator[Plugin]: yield from Plugins("univention.portal.extensions")
[docs] def get_dynamic_classes(klass_name: str) -> Plugin: for extension in get_all_dynamic_classes(): if klass_name == extension.__name__: return extension raise KeyError(klass_name)