reflection

Utilities for Python reflection.

import adulib.reflection

is_valid_python_name

is_valid_python_name(name: str) -> bool

assert is_valid_python_name('a_python_name')
assert not is_valid_python_name('not a valid python name')

find_module_root

find_module_root(path)

Recursively finds the root directory of a Python module.

This function takes a file or directory path and determines the root directory of the module it belongs to. A directory is considered a module if it contains an ‘init.py’ file. The function will traverse upwards in the directory hierarchy until it finds the top-most module directory.

Parameters: path (str or Path): The file or directory path to start the search from.

Returns: Path or None: The root directory of the module if found, otherwise None.


module_root = find_module_root(adulib.reflection.__file__)

get_module_path_hierarchy

get_module_path_hierarchy(path)

Get the hierarchy of module paths starting from the given path.

This function constructs a list of tuples representing the module hierarchy starting from the specified path. Each tuple contains the module name and its corresponding path.

Parameters: path (str or Path): The file or directory path to start the hierarchy search from.

Returns: list: A list of tuples where each tuple contains a module name and its path.


get_function_from_py_file

get_function_from_py_file(file_path, func_name, args, is_async, return_func_key)

Extracts and returns a function from a Python file.

This function reads a Python file, constructs a function from its contents, and returns it. It can handle both synchronous and asynchronous functions, and allows for optional argument specification and return value handling.

Parameters: file_path (str or Path): The path to the Python file containing the function. func_name (str, optional): The name of the function to extract. If not provided, the function name defaults to the file name without extension. args (list, optional): A list of argument names for the function. Defaults to an empty list. is_async (bool, optional): Indicates if the function is asynchronous. Defaults to False. return_func_key (str, optional): A key to handle return values within the function. Defaults to an empty string.

Returns: function: The extracted function, ready to be called with the specified arguments.


import tempfile
py_code = """
print('Hello...')
print(f'...{name}!')
"""

with tempfile.NamedTemporaryFile(delete=False, suffix=".py") as temp_file:
    temp_file.write(py_code.encode('utf-8'))
    temp_file_path = temp_file.name

func = get_function_from_py_file(temp_file_path, args=['name'])
func('world')
Hello...
...world!
py_code = """
import asyncio
await asyncio.sleep(0)
print('Hello...')
print(f'...{name}!')
"""

with tempfile.NamedTemporaryFile(delete=False, suffix=".py") as temp_file:
    temp_file.write(py_code.encode('utf-8'))
    temp_file_path = temp_file.name

func = get_function_from_py_file(temp_file_path, is_async=True, args=['name'])
await func('world')
Hello...
...world!

method_from_py_file

method_from_py_file(file_path: str)

A decorator that replaces the functionality of a method with the code from a specified Python file.

This decorator reads a Python file, extracts a function with the same name as the decorated method, and replaces the original method’s functionality with the extracted function. It supports both synchronous and asynchronous functions.

Arguments: - file_path (str): The path to the Python file containing the function to be used as a replacement.

Returns: function: A decorator that wraps the original function, replacing its functionality with the function from the specified file.


py_code = """
print(f'Hello {self.name}')
"""

with tempfile.NamedTemporaryFile(delete=False, suffix=".py") as temp_file:
    temp_file.write(py_code.encode('utf-8'))
    temp_file_path = temp_file.name

class TestClass:
    def __init__(self, name):
        self.name = name

    @method_from_py_file(temp_file_path)
    def print_name(self): pass
    
TestClass("world").print_name()
Hello world

mod_property

mod_property(func, cached)

Used to create module-level properties.


cached_mod_property

cached_mod_property(func)

@mod_property
def my_prop():
    print('my_prop called')
    return 42
def add_method(cls):
    def decorator(func):
        setattr(cls, func.__name__, func)
        return func
    return decorator

class MyClass:
    def __init__(self, value):
        self.value = value

@add_method(MyClass)
def double(self):
    return self.value * 2

@add_method(MyClass)
def triple(self):
    return self.value * 3

obj = MyClass(10)
print(obj.double())  # 20
print(obj.triple())  # 30
20
30

patch_to

patch_to(cls, as_prop, cls_method, set_prop)

Decorator: add f to cls


Define methods

class Foo:
    ...
    
@patch_to(Foo)
def bar(self):
    return 'bar'

Foo().bar()
'bar'

Define properties

class Foo:
    def __init__(self):
        self.value = "bar"

# Define a getter
@patch_to(Foo, as_prop=True)
def baz(self):
    return self.value

# Define a setter
@patch_to(Foo, set_prop=True)
def baz(self, value):
    self.value = value

foo = Foo()
assert foo.baz == "bar"
foo.baz = "???"
assert foo.baz == "???"

Define a class method

@patch_to(Foo, cls_method=True)
def qux(self):
    return 'bar'

Foo.qux()
'bar'

patch

patch(f, as_prop, cls_method, set_prop)

Decorator: add f to the first parameter’s class (based on f’s type annotations)


patch is similar to patch_to, except it uses type annotations in the signature to find the class to patch to.

class Foo:
    ...
    
@patch
def bar(self: Foo):
    return 'bar'

Foo().bar()
'bar'
class Foo:
    def __init__(self):
        self.value = "bar"

# Define a getter
@patch(as_prop=True)
def baz(self: Foo):
    return self.value

# Define a setter
@patch(set_prop=True)
def baz(self: Foo, value):
    self.value = value

foo = Foo()
assert foo.baz == "bar"
foo.baz = "???"
assert foo.baz == "???"
@patch(cls_method=True)
def qux(cls: Foo):
    return 'bar'

Foo.qux()
'bar'