Name a discovered module for where it sits under the synced directory

`fluksio sync dev` refused a one-file-per-directory layout: s1..s4 each
holding `study.py` stopped the walk with "two files would both import as
'study'". The suggested `__init__.py` per study directory fixed discovery
and broke the bare `from study import ...` a test beside it does. A file is
now named for its path under the directory being synced — `dev/s1/study.py`
imports as `s1.study`, the directories between being namespace packages —
so nothing collides and nothing has to be added. A file at the root keeps
its bare name, and the generated shim follows: `import_root` walks one
directory per dotted segment and lands on the same root.

The collision message stays for the spelling that can still reach it: two
files named on the command line, each rooted at its own directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hra4ndWMCLU5F3KjUuVAc
This commit is contained in:
2026-08-29 13:52:08 +02:00
co-authored by Claude Opus 5
parent 81649dbfca
commit de87151c60
2 changed files with 41 additions and 4 deletions
+10 -3
View File
@@ -136,8 +136,8 @@ def _import(root: str, dotted: str, expect: Path | None = None) -> None:
f" {actual}\n {expect}\n" f" {actual}\n {expect}\n"
"Python keeps one module per name, and a node's generated body " "Python keeps one module per name, and a node's generated body "
"imports by that name, so the second would run the first's code. " "imports by that name, so the second would run the first's code. "
"Put an `__init__.py` in each directory they become " "Sync the directory they are both under — each then imports as "
f"'<dir>.{dotted}' and stop colliding — or rename one of the files." "'<dir>.<file>' — or rename one of the files."
) )
@@ -211,7 +211,14 @@ def discover(targets: list[str], keep_going: bool = False) -> list[Flow]:
if entry.is_dir(): if entry.is_dir():
_import_package(entry) _import_package(entry)
else: else:
_import(*_module_of(entry), entry) # Named for where it sits under the directory being synced,
# so `dev/s1/study.py` imports as `s1.study` and one
# `study.py` per study collides with nothing. No
# `__init__.py` needed: the directories in between are
# namespace packages. A file at the root keeps its bare
# name, which is what it has always had.
dotted = ".".join(entry.relative_to(path).with_suffix("").parts)
_import(str(path), dotted, entry)
except SyncError: except SyncError:
# A name collision is never somebody else's problem: it would # A name collision is never somebody else's problem: it would
# put the wrong file behind a node. # put the wrong file behind a node.
+31 -1
View File
@@ -472,8 +472,38 @@ def test_a_study_in_a_subfolder_is_found(tmp_path) -> None:
} }
def test_a_study_per_directory_imports_under_its_own_name(tmp_path) -> None:
"""One `study.py` per folder is a layout people have, and it works.
Named for where each sits under the directory being synced, so nothing
collides and no `__init__.py` has to be added — which would break the
bare `from study import ...` a test beside it does.
"""
import sys
from fluksio.sdk.cli import discover
for study in ("s1", "s2"):
(tmp_path / "dev" / study).mkdir(parents=True)
(tmp_path / "dev" / study / "study.py").write_text(f"VALUE = {study!r}\n")
try:
discover([str(tmp_path / "dev")])
assert sys.modules["s1.study"].VALUE == "s1"
assert sys.modules["s2.study"].VALUE == "s2"
finally:
for name in ("s1.study", "s2.study", "s1", "s2"):
sys.modules.pop(name, None)
sys.path[:] = [entry for entry in sys.path if entry != str(tmp_path / "dev")]
def test_two_files_of_one_name_are_refused(tmp_path) -> None: 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.""" """Python keeps one module per name, and a node's body imports by it.
Unreachable from one sync of a directory now that a file is named for
where it sits; this is the spelling that still gets there — two files
named on the command line, each rooted at its own directory.
"""
import pytest import pytest
from fluksio.sdk import SyncError from fluksio.sdk import SyncError