From 034108e636de56e75f61e53f521f86ca955fd835 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 27 Oct 2026 20:01:25 +0000 Subject: [PATCH] Resolve CLI script paths and keep notebook conversion files beside their source --- great_ai/__main__.py | 19 +++++++++++++++---- tests/test_cli_regressions.py | 23 +++++++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/great_ai/__main__.py b/great_ai/__main__.py index b8416c7..9a930d2 100644 --- a/great_ai/__main__.py +++ b/great_ai/__main__.py @@ -2,6 +2,7 @@ import logging import re +import sys from importlib import import_module, reload from pathlib import Path from threading import Event @@ -143,12 +144,22 @@ def get_script_name(file_name_argument: str) -> str: with open(file_name_argument, "w", encoding="utf-8") as f: f.write(content) - return re.sub(r"\.(py|ipynb)$", "", file_name_argument) + if file_name_argument.endswith(".py"): + path = Path(file_name_argument).expanduser().resolve() + parts = [path.stem] + directory = path.parent + while (directory / "__init__.py").is_file(): + parts.insert(0, directory.name) + directory = directory.parent + if str(directory) not in sys.path: + sys.path.insert(0, str(directory)) + return ".".join(parts) + return file_name_argument def get_script_name_of_notebook(notebook_name: str) -> str: - base_name = re.sub(r"\.ipynb$", "", notebook_name) - return f"__{base_name}__.py" + path = Path(notebook_name) + return str(path.with_name(f"__{path.stem}__.py")) module = None @@ -159,7 +170,7 @@ def find_app(file_name: str) -> Optional[str]: logging.disable(logging.CRITICAL) try: - if module is None: + if module is None or module.__name__ != file_name: module = import_module(file_name) else: module = reload(module) diff --git a/tests/test_cli_regressions.py b/tests/test_cli_regressions.py index 11c3bb0..87b3347 100644 --- a/tests/test_cli_regressions.py +++ b/tests/test_cli_regressions.py @@ -1,11 +1,11 @@ import importlib +from pathlib import Path from threading import Event from unittest.mock import patch import pytest -from watchdog.events import FileClosedEvent, FileModifiedEvent, FileMovedEvent - from great_ai.deploy.reload_event_handler import ReloadEventHandler +from watchdog.events import FileClosedEvent, FileModifiedEvent, FileMovedEvent def test_reload_debounces_edit_and_replacement_events() -> None: @@ -43,3 +43,22 @@ def test_cli_operational_errors_exit_nonzero(module_name: str, operation: str) - with pytest.raises(SystemExit) as error: module.main() assert error.value.code == 1 + + +def test_cli_resolves_nested_scripts_and_notebooks( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + from great_ai.__main__ import get_script_name, get_script_name_of_notebook + + monkeypatch.syspath_prepend(str(tmp_path)) + package = tmp_path / "nested" + package.mkdir() + (package / "__init__.py").write_text("") + (package / "service.py").write_text("value = 7\n") + monkeypatch.chdir(tmp_path) + assert get_script_name("nested/service.py") == "nested.service" + assert importlib.import_module("nested.service").value == 7 + assert get_script_name(str(package / "service.py")) == "nested.service" + assert ( + get_script_name_of_notebook("nested/service.ipynb") == "nested/__service__.py" + )