22
33import importlib
44import pkgutil
5+ from functools import lru_cache
6+ from importlib .metadata import entry_points
57from inspect import isclass
68from typing import Any
79
1012_implementation_cache : dict [Any , list [Any ]] = {}
1113_subclass_cache : dict [Any , list [Any ]] = {}
1214
15+ # Entry point group name for aignostics plugins
16+ PLUGIN_ENTRY_POINT_GROUP = "aignostics.plugins"
17+
18+
19+ @lru_cache (maxsize = 1 )
20+ def discover_plugin_packages () -> tuple [str , ...]:
21+ """
22+ Discover plugin packages using entry points.
23+
24+ Plugins register themselves in their pyproject.toml:
25+
26+ [project.entry-points."aignostics.plugins"]
27+ my_plugin = "my_plugin"
28+
29+ Results are cached after the first call.
30+
31+ Returns:
32+ tuple[str, ...]: Tuple of discovered plugin package names.
33+ """
34+ eps = entry_points (group = PLUGIN_ENTRY_POINT_GROUP )
35+ return tuple (ep .value for ep in eps )
36+
1337
1438def load_modules () -> None :
1539 package = importlib .import_module (__project_name__ )
@@ -21,6 +45,8 @@ def locate_implementations(_class: type[Any]) -> list[Any]:
2145 """
2246 Dynamically discover all instances of some class.
2347
48+ Searches in the main project and all plugins registered via entry points.
49+
2450 Args:
2551 _class (type[Any]): Class to search for.
2652
@@ -30,16 +56,19 @@ def locate_implementations(_class: type[Any]) -> list[Any]:
3056 if _class in _implementation_cache :
3157 return _implementation_cache [_class ]
3258
59+ plugin_packages = discover_plugin_packages ()
60+
3361 implementations = []
34- package = importlib .import_module (__project_name__ )
62+ for package_name in [* plugin_packages , __project_name__ ]:
63+ package = importlib .import_module (package_name )
3564
36- for _ , name , _ in pkgutil .iter_modules (package .__path__ ):
37- module = importlib .import_module (f"{ __project_name__ } .{ name } " )
38- # Check all members of the module
39- for member_name in dir (module ):
40- member = getattr (module , member_name )
41- if isinstance (member , _class ):
42- implementations .append (member )
65+ for _ , name , _ in pkgutil .iter_modules (package .__path__ ):
66+ module = importlib .import_module (f"{ package_name } .{ name } " )
67+ # Check all members of the module
68+ for member_name in dir (module ):
69+ member = getattr (module , member_name )
70+ if isinstance (member , _class ):
71+ implementations .append (member )
4372
4473 _implementation_cache [_class ] = implementations
4574 return implementations
@@ -49,6 +78,8 @@ def locate_subclasses(_class: type[Any]) -> list[Any]:
4978 """
5079 Dynamically discover all classes that are subclasses of some type.
5180
81+ Searches in the main project and all plugins registered via entry points.
82+
5283 Args:
5384 _class (type[Any]): Parent class of subclasses to search for.
5485
@@ -58,19 +89,25 @@ def locate_subclasses(_class: type[Any]) -> list[Any]:
5889 if _class in _subclass_cache :
5990 return _subclass_cache [_class ]
6091
61- subclasses = []
62- package = importlib .import_module (__project_name__ )
92+ plugin_packages = discover_plugin_packages ()
6393
64- for _ , name , _ in pkgutil .iter_modules (package .__path__ ):
94+ subclasses = []
95+ for package_name in [* plugin_packages , __project_name__ ]:
6596 try :
66- module = importlib .import_module (f"{ __project_name__ } .{ name } " )
67- # Check all members of the module
68- for member_name in dir (module ):
69- member = getattr (module , member_name )
70- if isclass (member ) and issubclass (member , _class ) and member != _class :
71- subclasses .append (member )
97+ package = importlib .import_module (package_name )
7298 except ImportError :
7399 continue
74100
101+ for _ , name , _ in pkgutil .iter_modules (package .__path__ ):
102+ try :
103+ module = importlib .import_module (f"{ package_name } .{ name } " )
104+ # Check all members of the module
105+ for member_name in dir (module ):
106+ member = getattr (module , member_name )
107+ if isclass (member ) and issubclass (member , _class ) and member != _class :
108+ subclasses .append (member )
109+ except ImportError :
110+ continue
111+
75112 _subclass_cache [_class ] = subclasses
76113 return subclasses
0 commit comments