From 18467a6500855fc5d36e1a3a094fe5948c538653 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Sun, 25 Jan 2026 09:40:35 +0100 Subject: [PATCH] lib.Types: Add module Types is a container for types, notably classes, which are dynamically loaded from other modules. Which modules are loaded is based on the following criteria passed to its constructor: - mod_names: A list of modules to load and investigate - type_name_filter: A regular filter expression or None (default). If it's None, all types pass this filter. - type_filter: A list of types the returned types must match. Defaults to [], in which case all types pass this filter Signed-off-by: Jan Lindemann --- src/python/jw/pkg/lib/Types.py | 79 ++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/python/jw/pkg/lib/Types.py diff --git a/src/python/jw/pkg/lib/Types.py b/src/python/jw/pkg/lib/Types.py new file mode 100644 index 00000000..eb74c773 --- /dev/null +++ b/src/python/jw/pkg/lib/Types.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +from typing import TypeVar, Generic +import abc, re, sys +from collections.abc import Iterable, Iterator + +from .log import * + +T = TypeVar("T") + +class Types(abc.ABC, Iterable[T], Generic[T]): # export + + def __iter__(self) -> Iterator[T]: + return iter(self._classes()) + + @abc.abstractmethod + def _classes(self) -> Iterable[T]: + pass + + @property + def classes(self) -> Iterable[T]: + return self._classes() + + @abc.abstractmethod + def _stringify(self) -> list[str]: + pass + + def dump(self, prio: int, *args, **kwargs) -> None: + contents = self._stringify() + log(prio, ",--- ", *args, **kwargs) + for line in contents: + log(prio, "| " + line) + log(prio, "`--- ", *args, **kwargs) + +class LoadTypes(Types): # export + + def __init__(self, mod_names: list[str], type_name_filter: str=None, type_filter: list[T]=[]): + self.__type_name_filter = type_name_filter + self.__type_filter = type_filter + self.__mod_names = mod_names + self.__classes: list[type[Any]]|None = None + + def _stringify(self): + return [ + "type_name_filter: " + str(self.__type_name_filter), + "type_filter: " + ', '.join([str(f) for f in self.__type_filter]), + "mod_names: " + ', '.join(self.__mod_names) + ] + + def _classes(self) -> Iterable[T]: + if self.__classes is None: + import importlib, inspect + rx: Any|None = None + if self.__type_name_filter is not None: + rx = re.compile(self.__type_name_filter) + ret: list[Any] = [] + for mod_name in self.__mod_names: + if mod_name != '__main__': + importlib.import_module(mod_name) + for member_name, c in inspect.getmembers(sys.modules[mod_name], inspect.isclass): + if rx is not None and not re.match(rx, member_name): + log(DEBUG, 'o "{}.{}" has wrong name'.format(mod_name, member_name)) + continue + if inspect.isabstract(c): + log(DEBUG, 'o "{}.{}" is abstract'.format(mod_name, member_name)) + continue + if self.__type_filter: + for tp in self.__type_filter: + if issubclass(c, tp): + break + log(DEBUG, 'o "{}.{}" is not of type {}'.format(mod_name, member_name, tp)) + else: + log(DEBUG, 'o "{}.{}" doesn\'t match type filter'.format(mod_name, member_name)) + breakpoint() + continue + log(DEBUG, 'o "{}.{}" is fine, adding'.format(mod_name, member_name)) + ret.append(c) + self.__classes = ret + return self.__classes