88"""
99
1010import importlib
11+ import importlib .util
1112import typing
13+ import uuid
1214
1315CLASS_REF_DELIM : str = ':'
1416
1517class ClassReference :
1618 """
1719 A ClassReference is constructed from a formatted that references a specific Python class.
18- The basic structure of a class reference is: `[<path>:][<qualified package name>.][<module name>.]<class name>`.
19- This means that a valid class reference for this class can be all of the following:
20- 1) `reflection.ClassReference` -- a module and class name,
21- 2) `pacai.util.reflection.ClassReference` -- a fully qualified name.
22- 3) `pacai/util/reflection.py:ClassReference` -- a path and class name,
23- 4) `pacai/util/reflection.py:pacai.util.reflection.ClassReference` -- a path with a fully qualified name.
24-
25- Note that a class reference without a package name will need some default package path to look in.
20+ The rough basic structure of a class reference is: `[<path>:][<qualified package name>.][<module name>.]<class name>`.
21+ This means that a valid class reference for this class can either:
22+ 1) `pacai.util.reflection.ClassReference` -- a fully qualified name.
23+ 2) `pacai/util/reflection.py:ClassReference` -- a path and class name,
2624 """
2725
2826 def __init__ (self , text : str ) -> None :
@@ -51,6 +49,12 @@ def __init__(self, text: str) -> None:
5149 if (len (parts ) > 1 ):
5250 module_name = '.' .join (parts [0 :- 1 ]).strip ()
5351
52+ if ((filepath is not None ) and (module_name is not None )):
53+ raise ValueError ("Cannot specify both a filepath and module name for class reference: '%s'." , text )
54+
55+ if ((filepath is None ) and (module_name is None )):
56+ raise ValueError ("Cannot specify a class name alone, need a filepath or module name for class reference: '%s'." , text )
57+
5458 self .filepath : str | None = filepath
5559 """ The filepath component of the class reference (or None). """
5660
@@ -60,29 +64,48 @@ def __init__(self, text: str) -> None:
6064 self .class_name : str = class_name
6165 """ The class_name component of the class reference (or None). """
6266
63- def new_object (class_name : str , * args , ** kwargs ) -> typing .Any :
67+ def __str__ (self ) -> str :
68+ text = self .class_name
69+
70+ if (self .module_name is not None ):
71+ text = self .module_name + '.' + text
72+
73+ if (self .filepath is not None ):
74+ text = self .filepath + CLASS_REF_DELIM + text
75+
76+ return text
77+
78+ def new_object (class_ref : ClassReference | str , * args , ** kwargs ) -> typing .Any :
6479 """
65- Create a new instance of the specified class,
80+ Create a new instance of the specified class reference ,
6681 passing along the args and kwargs.
67- The class name should be fully qualified, e.g., 'pacai.core.agent.Agent', not just 'Agent'.
68-
69- The module must be importable (i.e., already in the PATH).
7082 """
7183
72- parts = class_name .split ('.' )
73- module_name = '.' .join (parts [0 :- 1 ])
74- target_name = parts [- 1 ]
75-
76- if (len (parts ) == 1 ):
77- raise ValueError (f"Non-qualified name supplied '{ class_name } '." )
84+ if (isinstance (class_ref , str )):
85+ class_ref = ClassReference (class_ref )
7886
79- try :
80- module = importlib .import_module (module_name )
81- except ImportError :
82- raise ValueError (f"Unable to locate module '{ module_name } '." )
87+ module = _import_module (class_ref )
8388
84- target_class = getattr (module , target_name , None )
89+ target_class = getattr (module , class_ref . class_name , None )
8590 if (target_class is None ):
86- raise ValueError (f"Cannot find class '{ target_name } ' in module ' { module_name } '." )
91+ raise ValueError (f"Cannot find class '{ class_ref . class_name } ' in class reference ' { class_ref } '." )
8792
8893 return target_class (* args , ** kwargs )
94+
95+ def _import_module (class_ref ):
96+ """
97+ Import and return the module for the given class reference.
98+ This may involve importing files.
99+ """
100+
101+ if (class_ref .filepath is not None ):
102+ temp_module_name = str (uuid .uuid4 ()).replace ('-' , '' )
103+ spec = importlib .util .spec_from_file_location (temp_module_name , class_ref .filepath )
104+ module = importlib .util .module_from_spec (spec )
105+ spec .loader .exec_module (module )
106+ return module
107+ else :
108+ try :
109+ return importlib .import_module (class_ref .module_name )
110+ except ImportError :
111+ raise ValueError (f"Unable to locate module '{ class_ref .module_name } '." )
0 commit comments