From de87151c60a6810b6309d6412115374bb469ef6c Mon Sep 17 00:00:00 2001 From: stroblme Date: Sat, 29 Aug 2026 13:52:08 +0200 Subject: [PATCH] Name a discovered module for where it sits under the synced directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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) Claude-Session: https://claude.ai/code/session_019Hra4ndWMCLU5F3KjUuVAc --- backend/fluksio/sdk/cli.py | 13 ++++++++++--- backend/tests/test_cli.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/backend/fluksio/sdk/cli.py b/backend/fluksio/sdk/cli.py index 66b22b1..4b25d85 100644 --- a/backend/fluksio/sdk/cli.py +++ b/backend/fluksio/sdk/cli.py @@ -136,8 +136,8 @@ def _import(root: str, dotted: str, expect: Path | None = None) -> None: f" {actual}\n {expect}\n" "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. " - "Put an `__init__.py` in each directory — they become " - f"'.{dotted}' and stop colliding — or rename one of the files." + "Sync the directory they are both under — each then imports as " + "'.' — 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(): _import_package(entry) 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: # A name collision is never somebody else's problem: it would # put the wrong file behind a node. diff --git a/backend/tests/test_cli.py b/backend/tests/test_cli.py index ce9e923..8ef1eaa 100644 --- a/backend/tests/test_cli.py +++ b/backend/tests/test_cli.py @@ -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: - """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 from fluksio.sdk import SyncError