From bbfcd0fc8f8816e9bf9e950d21c720be1b057af5 Mon Sep 17 00:00:00 2001 From: Tim Fliss Date: Tue, 30 Sep 2025 15:49:55 -0700 Subject: [PATCH 1/3] module_name parameter for compile_python --- linkml_runtime/utils/compile_python.py | 12 +++--- .../test_linkml_runtime_issue_2903.py | 39 +++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 tests/test_issues/test_linkml_runtime_issue_2903.py diff --git a/linkml_runtime/utils/compile_python.py b/linkml_runtime/utils/compile_python.py index 7afea08d..469f00db 100644 --- a/linkml_runtime/utils/compile_python.py +++ b/linkml_runtime/utils/compile_python.py @@ -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" 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 diff --git a/tests/test_issues/test_linkml_runtime_issue_2903.py b/tests/test_issues/test_linkml_runtime_issue_2903.py new file mode 100644 index 00000000..8683047d --- /dev/null +++ b/tests/test_issues/test_linkml_runtime_issue_2903.py @@ -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" From f7be37eb727f832529369840c5785d602ad48824 Mon Sep 17 00:00:00 2001 From: Tim Fliss Date: Mon, 20 Oct 2025 13:23:25 -0700 Subject: [PATCH 2/3] move compile_python test; fix docstrings --- linkml_runtime/utils/compile_python.py | 16 +++++++++------- .../test_compile_python.py} | 0 2 files changed, 9 insertions(+), 7 deletions(-) rename tests/{test_issues/test_linkml_runtime_issue_2903.py => test_utils/test_compile_python.py} (100%) diff --git a/linkml_runtime/utils/compile_python.py b/linkml_runtime/utils/compile_python.py index 469f00db..0e682ff4 100644 --- a/linkml_runtime/utils/compile_python.py +++ b/linkml_runtime/utils/compile_python.py @@ -7,8 +7,9 @@ def file_text(txt_or_fname: str) -> str: """ Determine whether text_or_fname is a file name or a string and, if a file name, read it - :param txt_or_fname: - :return: + + :param txt_or_fname: Text content or filename to read + :return: File content as string """ if len(txt_or_fname) > 4 and '\n' not in txt_or_fname: with open(txt_or_fname) as ef: @@ -16,13 +17,14 @@ def file_text(txt_or_fname: str) -> str: return txt_or_fname -def compile_python(text_or_fn: str, package_path: str | None = None, module_name: str | None = None) -> ModuleType: +def compile_python(text_or_fn: str, package_path: str = None, module_name: str = 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 module_name: to be used in an import statement, default 'test' - @return: Compiled 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 module_name: Module name to be used in an import statement, default 'test' + :return: Compiled module """ if module_name is None: module_name = "test" diff --git a/tests/test_issues/test_linkml_runtime_issue_2903.py b/tests/test_utils/test_compile_python.py similarity index 100% rename from tests/test_issues/test_linkml_runtime_issue_2903.py rename to tests/test_utils/test_compile_python.py From 53c7ad041ab21ef018a375ea83c76c0e1caa809b Mon Sep 17 00:00:00 2001 From: ialarmedalien Date: Thu, 23 Oct 2025 08:50:40 -0700 Subject: [PATCH 3/3] Implementing suggestions made in PR comments --- linkml_runtime/utils/compile_python.py | 6 ++- tests/test_utils/test_compile_python.py | 57 +++++++++++++++++-------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/linkml_runtime/utils/compile_python.py b/linkml_runtime/utils/compile_python.py index 8ff1c927..fbe6dd1e 100644 --- a/linkml_runtime/utils/compile_python.py +++ b/linkml_runtime/utils/compile_python.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from logging import warning @@ -17,7 +19,7 @@ def file_text(txt_or_fname: str) -> str: return txt_or_fname -def compile_python(text_or_fn: str, package_path: str = None, module_name: str = None) -> ModuleType: +def compile_python(text_or_fn: str, package_path: str | None = None, module_name: str | None = "test") -> ModuleType: """ Compile the text or file and return the resulting module @@ -26,7 +28,7 @@ def compile_python(text_or_fn: str, package_path: str = None, module_name: str = :param module_name: Used in an import statement, default 'test' :return: Compiled module """ - if module_name is None: + if not module_name: module_name = "test" python_txt = file_text(text_or_fn) if package_path is None and python_txt != text_or_fn: diff --git a/tests/test_utils/test_compile_python.py b/tests/test_utils/test_compile_python.py index 66ce89c2..6228036a 100644 --- a/tests/test_utils/test_compile_python.py +++ b/tests/test_utils/test_compile_python.py @@ -1,10 +1,14 @@ +from __future__ import annotations + +from types import ModuleType + import pytest from linkml_runtime.utils.compile_python import compile_python -@pytest.fixture -def module_1(): +@pytest.fixture(scope="module") +def base_module() -> str: return """ x: int = 2 @@ -13,28 +17,47 @@ def fun(value: int): """ -@pytest.fixture -def module_2(): +@pytest.fixture(scope="module") +def importing_module() -> str: return """ -import module_1 as m +import MODULE_NAME 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=".") +def check_generated_module(module: ModuleType, module_name: str) -> None: + assert isinstance(module, ModuleType) + assert module.__name__ == module_name + assert module.x == 2 + assert module.fun(3) == "known value 3" + + +@pytest.mark.parametrize(("name_arg", "module_name"), [(None, "test"), ("", "test"), ("base_module", "base_module")]) +def test_compile_python_module_name(base_module: str, name_arg: str | None, module_name: str) -> None: + """Test the compilation of python code to create a module.""" + m = compile_python(base_module, module_name=name_arg) + check_generated_module(m, module_name) + + +@pytest.mark.parametrize(("name_arg", "module_name"), [(None, "test"), ("", "test"), ("base_module", "base_module")]) +def test_compile_python_importing_module_local_module( + base_module: str, + importing_module: str, + name_arg: str | None, + module_name: str, +) -> None: + """Test the compilation of python code to create a local module and then compile a second module that imports the first.""" + m = compile_python(base_module, module_name=name_arg) + check_generated_module(m, module_name) + + # switch in the appropriate module name + importing_module_text = importing_module.replace("MODULE_NAME", module_name) + m2 = compile_python(importing_module_text, package_path=".", module_name="module_2") + assert isinstance(m2, ModuleType) 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" + # check the imported module, m2.m, has the correct type, name, etc. + check_generated_module(m2.m, module_name)