main
py 97 lines 2.94 KB
Raw
1
2 import re, os, importlib, importlib.util, inspect, sys
3 from types import ModuleType
4 from typing import Any, Type, TypeVar
5 from helpers.files import get_abs_path
6 from fnmatch import fnmatch
7
8
9 T = TypeVar("T") # Define a generic type variable
10
11
12 def import_module(file_path: str) -> ModuleType:
13 # Handle file paths with periods in the name using importlib.util
14 abs_path = get_abs_path(file_path)
15 module_name = os.path.basename(abs_path).replace(".py", "")
16
17 # Create the module spec and load the module
18 spec = importlib.util.spec_from_file_location(module_name, abs_path)
19 if spec is None or spec.loader is None:
20 raise ImportError(f"Could not load module from {abs_path}")
21
22 module = importlib.util.module_from_spec(spec)
23 spec.loader.exec_module(module)
24 return module
25
26
27 def load_classes_from_folder(
28 folder: str, name_pattern: str, base_class: Type[T], one_per_file: bool = True
29 ) -> list[Type[T]]:
30 classes = []
31 abs_folder = get_abs_path(folder)
32
33 # Get all .py files in the folder that match the pattern, sorted alphabetically
34 py_files = sorted(
35 [
36 file_name
37 for file_name in os.listdir(abs_folder)
38 if fnmatch(file_name, name_pattern) and file_name.endswith(".py")
39 ]
40 )
41
42 # Iterate through the sorted list of files
43 for file_name in py_files:
44 file_path = os.path.join(abs_folder, file_name)
45 # Use the new import_module function
46 module = import_module(file_path)
47
48 # Get all classes in the module
49 class_list = inspect.getmembers(module, inspect.isclass)
50
51 # Filter for classes that are subclasses of the given base_class
52 # iterate backwards to skip imported superclasses
53 for cls in reversed(class_list):
54 if cls[1] is not base_class and issubclass(cls[1], base_class):
55 classes.append(cls[1])
56 if one_per_file:
57 break
58
59 return classes
60
61
62 def load_classes_from_file(
63 file: str, base_class: type[T], one_per_file: bool = True
64 ) -> list[type[T]]:
65 classes = []
66 # Use the new import_module function
67 module = import_module(file)
68
69 # Get all classes in the module
70 class_list = inspect.getmembers(module, inspect.isclass)
71
72 # Filter for classes that are subclasses of the given base_class
73 # iterate backwards to skip imported superclasses
74 for cls in reversed(class_list):
75 if cls[1] is not base_class and issubclass(cls[1], base_class):
76 classes.append(cls[1])
77 if one_per_file:
78 break
79
80 return classes
81
82
83 def purge_namespace(namespace: str):
84 to_delete = [
85 name
86 for name in sys.modules
87 if name == namespace or name.startswith(namespace + ".")
88 ]
89
90 # delete deepest first just to be tidy
91 to_delete.sort(key=lambda n: n.count("."), reverse=True)
92
93 for name in to_delete:
94 del sys.modules[name]
95
96 importlib.invalidate_caches()
97 return to_delete