Refactor code to dynamically create classes from
strings
This commit is contained in:
parent
30cc5723bd
commit
783c0e6436
1 changed files with 76 additions and 12 deletions
|
|
@ -145,16 +145,46 @@ def create_function(code, function_name):
|
||||||
|
|
||||||
|
|
||||||
def create_class(code, class_name):
|
def create_class(code, class_name):
|
||||||
|
"""
|
||||||
|
Dynamically create a class from a string of code and a specified class name.
|
||||||
|
|
||||||
|
:param code: String containing the Python code defining the class
|
||||||
|
:param class_name: Name of the class to be created
|
||||||
|
:return: A function that, when called, returns an instance of the created class
|
||||||
|
"""
|
||||||
if not hasattr(ast, "TypeIgnore"):
|
if not hasattr(ast, "TypeIgnore"):
|
||||||
|
ast.TypeIgnore = create_type_ignore_class()
|
||||||
class TypeIgnore(ast.AST):
|
|
||||||
_fields = ()
|
|
||||||
|
|
||||||
ast.TypeIgnore = TypeIgnore
|
|
||||||
|
|
||||||
module = ast.parse(code)
|
module = ast.parse(code)
|
||||||
exec_globals = globals().copy()
|
exec_globals = prepare_global_scope(module)
|
||||||
|
|
||||||
|
class_code = extract_class_code(module, class_name)
|
||||||
|
compiled_class = compile_class_code(class_code)
|
||||||
|
|
||||||
|
return build_class_constructor(compiled_class, exec_globals, class_name)
|
||||||
|
|
||||||
|
|
||||||
|
def create_type_ignore_class():
|
||||||
|
"""
|
||||||
|
Create a TypeIgnore class for AST module if it doesn't exist.
|
||||||
|
|
||||||
|
:return: TypeIgnore class
|
||||||
|
"""
|
||||||
|
|
||||||
|
class TypeIgnore(ast.AST):
|
||||||
|
_fields = ()
|
||||||
|
|
||||||
|
return TypeIgnore
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_global_scope(module):
|
||||||
|
"""
|
||||||
|
Prepares the global scope with necessary imports from the provided code module.
|
||||||
|
|
||||||
|
:param module: AST parsed module
|
||||||
|
:return: Dictionary representing the global scope with imported modules
|
||||||
|
"""
|
||||||
|
exec_globals = globals().copy()
|
||||||
for node in module.body:
|
for node in module.body:
|
||||||
if isinstance(node, ast.Import):
|
if isinstance(node, ast.Import):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
|
|
@ -169,17 +199,47 @@ def create_class(code, class_name):
|
||||||
exec_globals[alias.name] = getattr(imported_module, alias.name)
|
exec_globals[alias.name] = getattr(imported_module, alias.name)
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError(f"Module {node.module} not found. Please install it and try again.") from e
|
raise ModuleNotFoundError(f"Module {node.module} not found. Please install it and try again.") from e
|
||||||
|
return exec_globals
|
||||||
|
|
||||||
|
|
||||||
|
def extract_class_code(module, class_name):
|
||||||
|
"""
|
||||||
|
Extracts the AST node for the specified class from the module.
|
||||||
|
|
||||||
|
:param module: AST parsed module
|
||||||
|
:param class_name: Name of the class to extract
|
||||||
|
:return: AST node of the specified class
|
||||||
|
"""
|
||||||
class_code = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name)
|
class_code = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name)
|
||||||
class_code.parent = None
|
class_code.parent = None
|
||||||
|
return class_code
|
||||||
|
|
||||||
|
|
||||||
|
def compile_class_code(class_code):
|
||||||
|
"""
|
||||||
|
Compiles the AST node of a class into a code object.
|
||||||
|
|
||||||
|
:param class_code: AST node of the class
|
||||||
|
:return: Compiled code object of the class
|
||||||
|
"""
|
||||||
code_obj = compile(ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec")
|
code_obj = compile(ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec")
|
||||||
# This suppresses import errors
|
return code_obj
|
||||||
# with contextlib.suppress(Exception):
|
|
||||||
exec(code_obj, exec_globals, locals())
|
|
||||||
|
def build_class_constructor(compiled_class, exec_globals, class_name):
|
||||||
|
"""
|
||||||
|
Builds a constructor function for the dynamically created class.
|
||||||
|
|
||||||
|
:param compiled_class: Compiled code object of the class
|
||||||
|
:param exec_globals: Global scope with necessary imports
|
||||||
|
:param class_name: Name of the class
|
||||||
|
:return: Constructor function for the class
|
||||||
|
"""
|
||||||
|
exec(compiled_class, exec_globals, locals())
|
||||||
exec_globals[class_name] = locals()[class_name]
|
exec_globals[class_name] = locals()[class_name]
|
||||||
|
|
||||||
# Return a function that imports necessary modules and creates an instance of the target class
|
# Return a function that imports necessary modules and creates an instance of the target class
|
||||||
def build_my_class(*args, **kwargs):
|
def build_custom_class(*args, **kwargs):
|
||||||
for module_name, module in exec_globals.items():
|
for module_name, module in exec_globals.items():
|
||||||
if isinstance(module, type(importlib)):
|
if isinstance(module, type(importlib)):
|
||||||
globals()[module_name] = module
|
globals()[module_name] = module
|
||||||
|
|
@ -187,9 +247,13 @@ def create_class(code, class_name):
|
||||||
instance = exec_globals[class_name](*args, **kwargs)
|
instance = exec_globals[class_name](*args, **kwargs)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
build_my_class.__globals__.update(exec_globals)
|
build_custom_class.__globals__.update(exec_globals)
|
||||||
|
return build_custom_class
|
||||||
|
|
||||||
return build_my_class
|
|
||||||
|
# Example usage:
|
||||||
|
# class_builder = create_class("class MyClass: pass", "MyClass")
|
||||||
|
# my_instance = class_builder()
|
||||||
|
|
||||||
|
|
||||||
def extract_function_name(code):
|
def extract_function_name(code):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue