Resolve CLI script paths and keep notebook conversion files beside their source

This commit is contained in:
Andras Schmelczer 2026-10-27 20:01:25 +00:00
commit 034108e636
2 changed files with 36 additions and 6 deletions

View file

@ -2,6 +2,7 @@
import logging import logging
import re import re
import sys
from importlib import import_module, reload from importlib import import_module, reload
from pathlib import Path from pathlib import Path
from threading import Event 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: with open(file_name_argument, "w", encoding="utf-8") as f:
f.write(content) 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: def get_script_name_of_notebook(notebook_name: str) -> str:
base_name = re.sub(r"\.ipynb$", "", notebook_name) path = Path(notebook_name)
return f"__{base_name}__.py" return str(path.with_name(f"__{path.stem}__.py"))
module = None module = None
@ -159,7 +170,7 @@ def find_app(file_name: str) -> Optional[str]:
logging.disable(logging.CRITICAL) logging.disable(logging.CRITICAL)
try: try:
if module is None: if module is None or module.__name__ != file_name:
module = import_module(file_name) module = import_module(file_name)
else: else:
module = reload(module) module = reload(module)

View file

@ -1,11 +1,11 @@
import importlib import importlib
from pathlib import Path
from threading import Event from threading import Event
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from watchdog.events import FileClosedEvent, FileModifiedEvent, FileMovedEvent
from great_ai.deploy.reload_event_handler import ReloadEventHandler 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: 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: with pytest.raises(SystemExit) as error:
module.main() module.main()
assert error.value.code == 1 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"
)