Sync only the declaring module before a run; skip git-ignored paths

`fluksio run` walked the working directory and imported every module in
it, which ran a research repository's analysis scripts before the run was
submitted. A flow's origin now records the file and module name that
declared it, and run/sweep import just that, under the same name so the
generated node bodies do not change. A flow with no record still walks,
now skipping what .gitignore covers.

Every code-defined flow reports `flow` changed once on its next sync,
since the origin gained two fields.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgG6TJw5qBZzaLghQSRVrq
This commit is contained in:
2026-09-16 16:55:02 +02:00
co-authored by Claude Opus 5
parent 7dfe646e2b
commit fccf89f849
9 changed files with 246 additions and 22 deletions
+79
View File
@@ -550,6 +550,85 @@ def test_a_study_per_directory_imports_under_its_own_name(tmp_path) -> None:
sys.path[:] = [entry for entry in sys.path if entry != str(tmp_path / "dev")]
def test_run_imports_the_module_that_declared_the_flow(tmp_path, monkeypatch):
"""Not every script below the working directory: importing one runs it.
Imported under the name the last sync recorded, so the node bodies it
generates are the ones already stored. With nothing recorded, the walk.
"""
from fluksio.sdk import cli as sdk_cli
study = tmp_path / "dev" / "s1" / "study.py"
study.parent.mkdir(parents=True)
study.write_text("")
monkeypatch.chdir(tmp_path)
imported: list[tuple] = []
walked: list[list[str]] = []
monkeypatch.setattr(sdk_cli, "_import", lambda *args: imported.append(args))
monkeypatch.setattr(
sdk_cli,
"discover",
lambda targets, keep_going=False: walked.append(targets) or [],
)
class Engine:
origin: dict = {}
def get_flow(self, name):
return {"definition": {"origin": self.origin}}
engine = Engine()
engine.origin = {"declared_in": "dev/s1/study.py", "module": "s1.study"}
sdk_cli._sync_first(engine, [], "study")
assert imported == [
(str(tmp_path / "dev"), "s1.study", tmp_path / "dev" / "s1" / "study.py")
]
assert walked == []
# A name that does not match the file is not trusted, and neither is a
# file this checkout does not have: both walk, as an unrecorded flow does.
for origin in (
{"declared_in": "dev/s1/study.py", "module": "s2.study"},
{"declared_in": "dev/s9/study.py", "module": "s9.study"},
{},
):
engine.origin = origin
sdk_cli._sync_first(engine, [], "study")
assert walked == [["."], ["."], ["."]]
# And `--sync` is taken as said.
sdk_cli._sync_first(engine, ["dev/s1"], "study")
assert walked[-1] == ["dev/s1"]
def test_the_walk_skips_what_the_repository_ignores(tmp_path, monkeypatch) -> None:
"""`mlruns/` is output, not a study, and importing it would run it."""
import shutil
import subprocess
import sys
import pytest
from fluksio.sdk.cli import discover
if shutil.which("git") is None:
pytest.skip("needs git")
subprocess.run(["git", "init", "-q", str(tmp_path)], check=True)
(tmp_path / ".gitignore").write_text("mlruns/\nscratch.py\n")
(tmp_path / "mlruns" / "0").mkdir(parents=True)
(tmp_path / "mlruns" / "0" / "boom.py").write_text("raise SystemExit('imported')\n")
(tmp_path / "scratch.py").write_text("raise SystemExit('imported')\n")
(tmp_path / "walked_study.py").write_text("VALUE = 1\n")
try:
discover([str(tmp_path)])
assert sys.modules["walked_study"].VALUE == 1
finally:
sys.modules.pop("walked_study", None)
sys.path[:] = [entry for entry in sys.path if entry != str(tmp_path)]
def test_two_files_of_one_name_are_refused(tmp_path) -> None:
"""Python keeps one module per name, and a node's body imports by it.