@@ -17,6 +17,64 @@ def default_processors_dir() -> str:
1717 return str (path )
1818
1919
20+ def _processor_base_class ():
21+ from dlclive import Processor
22+
23+ return Processor
24+
25+
26+ def _is_processor_subclass (obj , * , include_base : bool = False ) -> bool :
27+ """Return True for dlclive.Processor subclasses, including indirect subclasses."""
28+ if not inspect .isclass (obj ):
29+ return False
30+
31+ try :
32+ processor_base = _processor_base_class ()
33+ except Exception :
34+ logger .exception ("Could not import dlclive.Processor" )
35+ return False
36+
37+ try :
38+ if obj is processor_base :
39+ return bool (include_base )
40+ return issubclass (obj , processor_base )
41+ except TypeError :
42+ return False
43+
44+
45+ def _processor_info_from_class (cls , fallback_name : str ) -> dict :
46+ return {
47+ "class" : cls ,
48+ "name" : getattr (cls , "PROCESSOR_NAME" , fallback_name ),
49+ "description" : getattr (cls , "PROCESSOR_DESCRIPTION" , "" ),
50+ "params" : getattr (cls , "PROCESSOR_PARAMS" , {}),
51+ }
52+
53+
54+ def discover_processor_classes (module , * , only_defined_in_module : bool = True ) -> dict [str , dict ]:
55+ """Discover dlclive.Processor subclasses in a module.
56+
57+ Includes indirect subclasses of Processor.
58+
59+ Args:
60+ module: Imported Python module.
61+ only_defined_in_module: If True, ignore Processor subclasses imported
62+ from other modules to avoid duplicate registry entries.
63+ """
64+ processors : dict [str , dict ] = {}
65+
66+ for name , obj in inspect .getmembers (module , inspect .isclass ):
67+ if only_defined_in_module and getattr (obj , "__module__" , None ) != module .__name__ :
68+ continue
69+
70+ if not _is_processor_subclass (obj ):
71+ continue
72+
73+ processors [name ] = _processor_info_from_class (obj , name )
74+
75+ return processors
76+
77+
2078def scan_processor_folder (folder_path ):
2179 all_processors = {}
2280 folder = Path (folder_path )
@@ -65,22 +123,7 @@ def scan_processor_package(package_name: str = "dlclivegui.processors") -> dict[
65123 processors = mod .get_available_processors ()
66124 else :
67125 # Fallback: scan for dlclive.Processor subclasses
68- from dlclive import Processor
69-
70- processors = {}
71- for attr_name in dir (mod ):
72- obj = getattr (mod , attr_name )
73- try :
74- if isinstance (obj , type ) and obj is not Processor and issubclass (obj , Processor ):
75- processors [attr_name ] = {
76- "class" : obj ,
77- "name" : getattr (obj , "PROCESSOR_NAME" , attr_name ),
78- "description" : getattr (obj , "PROCESSOR_DESCRIPTION" , "" ),
79- "params" : getattr (obj , "PROCESSOR_PARAMS" , {}),
80- }
81- except Exception :
82- # Non-class or weird metaclass; ignore
83- pass
126+ processors = discover_processor_classes (mod )
84127
85128 # Normalize into your “file::class” shape
86129 module_file = mod .__name__ .split ("." )[- 1 ] + ".py"
@@ -131,26 +174,7 @@ def load_processors_from_file(file_path: str | Path):
131174 return processors
132175
133176 # Fallback path: discover subclasses of dlclive.Processor
134- from dlclive import Processor
135-
136- processors : dict [str , dict ] = {}
137- for name , obj in inspect .getmembers (module , inspect .isclass ):
138- if obj is Processor :
139- continue
140- # Guard: module might define other classes; only include Processor subclasses
141- try :
142- if issubclass (obj , Processor ):
143- processors [name ] = {
144- "class" : obj ,
145- "name" : getattr (obj , "PROCESSOR_NAME" , name ),
146- "description" : getattr (obj , "PROCESSOR_DESCRIPTION" , "" ),
147- "params" : getattr (obj , "PROCESSOR_PARAMS" , {}),
148- }
149- except Exception :
150- # Some "classes" can fail issubclass checks; ignore safely
151- continue
152-
153- return processors
177+ return discover_processor_classes (module )
154178
155179 except Exception :
156180 # Full traceback helps a ton when a plugin fails to import
0 commit comments