Skip to content
12 changes: 7 additions & 5 deletions linkml_runtime/utils/compile_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ def file_text(txt_or_fname: str) -> str:
return txt_or_fname


def compile_python(text_or_fn: str, package_path: str = None) -> ModuleType:
def compile_python(text_or_fn: str, package_path: str | None = None, module_name: str | None = None) -> ModuleType:
"""
Compile the text or file and return the resulting module
@param text_or_fn: Python text or file name that references python file
@param package_path: Root package path. If omitted and we've got a python file, the package is the containing
directory
@param package_path: Root package path. If omitted and we've got a python file, the package is the containing directory
@param module_name: to be used in an import statement, default 'test'
@return: Compiled module
"""
if module_name is None:
module_name = "test"
Copy link
Collaborator

@ialarmedalien ialarmedalien Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest using if not module_name instead of None -- otherwise, it's possible to create a module with an empty string as the name. I'd also set the default to "test", rather than None, as checking that the module_name is not falsy will catch "" and set it to "test" instead.

python_txt = file_text(text_or_fn)
if package_path is None and python_txt != text_or_fn:
package_path = text_or_fn
spec = compile(python_txt, 'test', 'exec')
module = ModuleType('test')
spec = compile(python_txt, module_name, 'exec')
module = ModuleType(module_name)
if package_path:
package_path_abs = os.path.join(os.getcwd(), package_path)
# We have to calculate the path to expected path relative to the current working directory
Expand Down
39 changes: 39 additions & 0 deletions tests/test_issues/test_linkml_runtime_issue_2903.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import pytest
from linkml_runtime.utils.compile_python import compile_python


@pytest.fixture
def module_1():
return """
x: int = 2

def fun(value: int):
return f'known value {value}'
"""


@pytest.fixture
def module_2():
return """
import module_1 as m

def more_fun(message: str):
return f'got "{message}"'
"""


def test_compile(module_1, module_2):
m1 = compile_python(module_1, module_name="module_1")
assert m1.__name__ == "module_1"
assert m1.x == 2
assert m1.fun(3) == "known value 3"
m2 = compile_python(module_2, module_name="module_2", package_path=".")
assert m2.__name__ == "module_2"
assert m2.more_fun("hello") == 'got "hello"'
assert m2.m.fun(4) == "known value 4"
assert m2.m.x == 2


def test_default_module_name(module_1):
m = compile_python(module_1)
assert m.__name__ == "test"