Source code for univention.listener.handler_configuration
# SPDX-FileCopyrightText: 2017-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
from __future__ import annotations
import inspect
import string
from typing import TYPE_CHECKING, Any
import listener
from .exceptions import ListenerModuleConfigurationError
from .handler_logging import get_logger
if TYPE_CHECKING:
from .handler import ListenerModuleHandler
listener.configRegistry.load()
[docs]
class ListenerModuleConfiguration:
"""
Interface class for accessing the configuration and code of a listener
module.
Subclass this and set the class attributes or pass them through `__init__`.
If more logic is needed, overwrite the corresponding
`get_<attribute>` method. Setting `name`, `description`, `ldap_filter` and
`listener_module_class` is mandatory.
To extend the configuration, add key names in :py:meth:`get_configuration_keys()`
and create a `get_<attribute>` method.
The listener server will use an object of your subclass to access your
listener module through :py:meth:`get_listener_module_instance()`.
"""
name = '' # (**) name of the listener module
description = '' # (*) description of the listener module
ldap_filter = '' # (*) LDAP filter, if matched will trigger the listener module
listener_module_class: type[ListenerModuleHandler] = None # (**) class that implements the module
attributes: list[str] = [] # only trigger module, if any of the listed attributes has changed
# (*) required
# (**) will be set automatically by the handlers metaclass
_mandatory_attributes: tuple[str, ...] = ('description', 'ldap_filter', 'listener_module_class')
def __init__(self, *args: Any, **kwargs: Any) -> None:
_keys = self.get_configuration_keys()
for k, _v in list(kwargs.items()):
if k in _keys:
setattr(self, k, kwargs.pop(k))
self.logger = get_logger(self.get_name())
self._run_checks()
def __repr__(self) -> str:
return f'{self.__class__.__name__}(name={self.get_name()!r})'
def _run_checks(self) -> None:
allowed_name_chars = string.ascii_letters + string.digits + ',.-_'
for attr in self._mandatory_attributes:
if not getattr(self, f'get_{attr}', lambda: '')() and not getattr(self, attr, ''):
raise ListenerModuleConfigurationError(f'Missing or empty {attr!r} attribute in configuration.')
if set(self.get_name()) - set(allowed_name_chars):
raise ListenerModuleConfigurationError(
f'The "name" of a listener module may only contain the following characters: {allowed_name_chars!r}',
)
if not inspect.isclass(self.get_listener_module_class()):
raise ListenerModuleConfigurationError('Attribute "listener_module_class" must be a class.')
[docs]
@classmethod
def get_configuration_keys(cls) -> list[str]:
"""
List of known configuration keys. Subclasses can expand this to support
additional attributes.
:return: list of known configuration keys
:rtype: list(str)
"""
return [
'attributes',
'description',
'ldap_filter',
'listener_module_class',
'name',
]
[docs]
def get_name(self) -> str:
"""
:return: name of module
:rtype: str
"""
return self.name
[docs]
def get_description(self) -> str:
"""
:return: description string of module
:rtype: str
"""
return self.description
[docs]
def get_ldap_filter(self) -> str:
"""
:return: LDAP filter of module
:rtype: str
"""
return self.ldap_filter
[docs]
def get_attributes(self) -> list[str]:
"""
:return: attributes of matching LDAP objects the module will be notified about if changed
:rtype: list(str)
"""
assert isinstance(self.attributes, list)
return self.attributes
[docs]
def get_priority(self) -> float:
"""
:return: priority of the handler. Defines the order in which this module is executed inside the listener
:rtype: float
"""
priority = getattr(self, "priority", 50.0)
return float(priority)
[docs]
def get_listener_module_instance(self, *args: Any, **kwargs: Any) -> ListenerModuleHandler:
"""
Get an instance of the listener module.
:param tuple args: passed to `__init__` of :py:class:`ListenerModuleHandler`
:param dict kwargs: : passed to `__init__` of :py:class:`ListenerModuleHandler`
:return: instance of :py:class:`ListenerModuleHandler`
:rtype: ListenerModuleHandler
"""
cls = self.get_listener_module_class()
return cls(*args, **kwargs)
[docs]
def get_listener_module_class(self) -> type[ListenerModuleHandler]:
"""
Get the class to instantiate for a listener module.
:return: subclass of :py:class:`univention.listener.ListenerModuleHandler`
:rtype: ListenerModuleHandler
"""
return self.listener_module_class
[docs]
def get_active(self) -> bool:
"""
If this listener module should run. Determined by the value of
`listener/module/<name>/deactivate`.
:return: whether the listener module should be activated
:rtype: bool
"""
return not listener.configRegistry.is_true(f'listener/module/{self.get_name()}/deactivate', False)