From 1e626c9cb0cdbaae9bbea2ee05a3e85483f935d4 Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Tue, 14 Apr 2026 20:47:53 +0000 Subject: [PATCH 1/9] feat: Enhance detection of affected models by adding traceable infrastructure classification --- scripts/detect_affected_models.py | 40 ++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/scripts/detect_affected_models.py b/scripts/detect_affected_models.py index fc8878ea..8fc3caa4 100644 --- a/scripts/detect_affected_models.py +++ b/scripts/detect_affected_models.py @@ -48,7 +48,12 @@ "src/mobius/models/__init__.py", ) -_SHARED_INFRA_PREFIXES = ( +_SHARED_INFRA_PREFIXES: tuple[str, ...] = () + +# Traceable infrastructure: component/task files that are analyzed via the +# import graph to find which models they actually affect, rather than +# triggering run_all unconditionally. +_TRACEABLE_PREFIXES = ( "src/mobius/components/", "src/mobius/tasks/", ) @@ -57,7 +62,7 @@ def classify_file(path: str) -> str: """Classify a changed file path. - Returns one of: 'model', 'component', 'task', 'shared_infra', + Returns one of: 'model', 'traceable', 'shared_infra', 'test', 'other'. """ normalized = path.replace("\\", "/") @@ -82,6 +87,11 @@ def classify_file(path: str) -> str: if normalized.startswith(prefix): return "shared_infra" + # Traceable infrastructure (components, tasks) — traced via import graph + for prefix in _TRACEABLE_PREFIXES: + if normalized.startswith(prefix): + return "traceable" + # Model files if rel.startswith("models/") and not rel.endswith("_test.py"): return "model" @@ -409,6 +419,7 @@ def detect_affected_models( # Classify files model_files: list[str] = [] + traceable_files: list[str] = [] for path in changed_files: category = classify_file(path) if category == "shared_infra": @@ -421,11 +432,17 @@ def detect_affected_models( run_all = True break model_files.append(path) + elif category == "traceable": + full_path = _PROJECT_ROOT / path + if not full_path.exists(): + run_all = True + break + traceable_files.append(path) if run_all: return {"affected": [], "run_all": True} - if not model_files: + if not model_files and not traceable_files: return {"affected": [], "run_all": False} # Build the registry map: source_module → [model_types] @@ -434,6 +451,7 @@ def detect_affected_models( # Build import graph for transitive analysis import_graph = _build_import_graph(_SRC_ROOT) + # Process model files: direct mapping + transitive dependents for path in model_files: normalized = path.replace("\\", "/") rel = normalized[len("src/mobius/") :] @@ -451,6 +469,22 @@ def detect_affected_models( if dep_module in registry_map: affected.update(registry_map[dep_module]) + # Process traceable files (components, tasks): find which models + # transitively import them, then map to registered model_types. + for path in traceable_files: + normalized = path.replace("\\", "/") + # Convert path to module name: src/mobius/components/_attention.py + # → mobius.components._attention + rel = normalized[len("src/") :] + module_name = rel[:-3].replace("/", ".") # strip .py, dots + if not module_name: + continue + + dependents = _find_reverse_dependents(module_name, import_graph) + for dep_module in dependents: + if dep_module in registry_map: + affected.update(registry_map[dep_module]) + return {"affected": sorted(affected), "run_all": False} From 5fac455ba7ba8d5ecf67f0b31167f760f751dd65 Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Tue, 14 Apr 2026 21:06:18 +0000 Subject: [PATCH 2/9] feat: Extend registry parsing to handle declarative dict registrations --- scripts/detect_affected_models.py | 34 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/scripts/detect_affected_models.py b/scripts/detect_affected_models.py index 8fc3caa4..1f26b044 100644 --- a/scripts/detect_affected_models.py +++ b/scripts/detect_affected_models.py @@ -246,10 +246,11 @@ def _build_class_to_source_module() -> dict[str, str]: def _build_registry_class_to_types() -> dict[str, list[str]]: """Parse _registry.py to map class names to registered model_types. - Handles three patterns: + Handles four patterns: 1. Direct: reg.register("name", ClassName) 2. For-loop: for name in (...): reg.register(name, ClassName) 3. Dict-loop: for name, cls in {...}.items(): reg.register(name, cls) + 4. Declarative dict: _REGISTRATIONS = {"name": ModelRegistration(ClassName, ...)} """ registry_file = _SRC_ROOT / "_registry.py" class_to_types: dict[str, list[str]] = {} @@ -271,6 +272,21 @@ def _build_registry_class_to_types() -> dict[str, list[str]]: if isinstance(node, ast.For): _process_for_loop(node, class_to_types) + # Pattern 4: _REGISTRATIONS = {"name": ModelRegistration(Cls, ...)} + # Handles both plain assignment and type-annotated assignment + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == "_REGISTRATIONS": + if isinstance(node.value, ast.Dict): + _process_registrations_dict(node.value, class_to_types) + if isinstance(node, ast.AnnAssign): + if ( + isinstance(node.target, ast.Name) + and node.target.id == "_REGISTRATIONS" + and isinstance(node.value, ast.Dict) + ): + _process_registrations_dict(node.value, class_to_types) + return {c: sorted(set(t)) for c, t in class_to_types.items()} @@ -348,6 +364,22 @@ def _process_for_loop( class_to_types.setdefault(value.id, []).append(key.value) +def _process_registrations_dict( + dict_node: ast.Dict, + class_to_types: dict[str, list[str]], +) -> None: + """Extract model_type → class from _REGISTRATIONS = {"name": ModelRegistration(Cls)}.""" + for key, value in zip(dict_node.keys, dict_node.values): + if not (isinstance(key, ast.Constant) and isinstance(key.value, str)): + continue + arch_name = key.value + # value is ModelRegistration(ClassName, ...) — extract the first arg + if isinstance(value, ast.Call) and value.args: + cls_arg = value.args[0] + if isinstance(cls_arg, ast.Name): + class_to_types.setdefault(cls_arg.id, []).append(arch_name) + + def _extract_string_constants(node: ast.expr) -> list[str]: """Extract string constants from a Tuple or List AST node.""" if isinstance(node, (ast.Tuple, ast.List)): From 51300f96101a2700619c739f47a169b77ed8de8d Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Tue, 14 Apr 2026 21:27:39 +0000 Subject: [PATCH 3/9] feat: Added test cases to handle new traceable logic --- scripts/detect_affected_models_test.py | 121 +++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 7 deletions(-) diff --git a/scripts/detect_affected_models_test.py b/scripts/detect_affected_models_test.py index 21db5404..155d5dcb 100644 --- a/scripts/detect_affected_models_test.py +++ b/scripts/detect_affected_models_test.py @@ -24,6 +24,7 @@ _build_registry_class_to_types, _build_source_module_to_types, _find_reverse_dependents, + _SRC_ROOT, classify_file, detect_affected_models, ) @@ -42,10 +43,10 @@ def test_model_init_is_shared_infra(self): assert classify_file("src/mobius/models/__init__.py") == "shared_infra" def test_component_file(self): - assert classify_file("src/mobius/components/_attention.py") == "shared_infra" + assert classify_file("src/mobius/components/_attention.py") == "traceable" def test_task_file(self): - assert classify_file("src/mobius/tasks/_causal_lm.py") == "shared_infra" + assert classify_file("src/mobius/tasks/_causal_lm.py") == "traceable" def test_configs_file(self): assert classify_file("src/mobius/_configs.py") == "shared_infra" @@ -116,7 +117,7 @@ def test_registry_has_falcon(self): assert "FalconCausalLMModel" in mapping types = mapping["FalconCausalLMModel"] assert "falcon" in types - assert "bloom" in types + assert "falcon_h1" in types def test_source_module_to_types(self): mapping = _build_source_module_to_types() @@ -173,13 +174,19 @@ def test_reverse_dependents_no_self(self): class TestDetectAffectedModels: - def test_component_change_triggers_run_all(self): + def test_component_change_traces_affected_models(self): + """A component change traces through the import graph to find affected models.""" result = detect_affected_models(["src/mobius/components/_attention.py"]) - assert result["run_all"] is True + assert result["run_all"] is False + # _attention.py is imported by many models — should find affected types + assert len(result["affected"]) > 0 - def test_task_change_triggers_run_all(self): + def test_task_change_traces_affected_models(self): + """A task change traces through the import graph to find affected models.""" result = detect_affected_models(["src/mobius/tasks/_causal_lm.py"]) - assert result["run_all"] is True + assert result["run_all"] is False + # _causal_lm.py is imported by task infrastructure — may affect models + # The key point: it does NOT trigger run_all def test_configs_change_triggers_run_all(self): result = detect_affected_models(["src/mobius/_configs.py"]) @@ -261,6 +268,106 @@ def test_empty_input(self): assert result["run_all"] is False assert result["affected"] == [] + def test_component_common_affects_many_models(self): + """_common.py is foundational — tracing should find many models.""" + result = detect_affected_models(["src/mobius/components/_common.py"]) + assert result["run_all"] is False + # _common.py defines Linear, Embedding, LayerNorm — used everywhere + assert len(result["affected"]) > 10 + + def test_shared_infra_still_triggers_run_all(self): + """True shared_infra files (_configs, _registry, etc.) still trigger run_all.""" + for path in [ + "src/mobius/_configs.py", + "src/mobius/_registry.py", + "src/mobius/_builder.py", + "src/mobius/_weight_loading.py", + "src/mobius/_model_package.py", + "src/mobius/_exporter.py", + "src/mobius/models/__init__.py", + "tests/conftest.py", + "tests/_test_configs.py", + ]: + result = detect_affected_models([path]) + assert result["run_all"] is True, ( + f"{path} should trigger run_all but didn't" + ) + + def test_traceable_and_model_combined(self): + """A component + model file change returns union of affected types.""" + result = detect_affected_models( + [ + "src/mobius/models/falcon.py", + "src/mobius/components/_attention.py", + ] + ) + assert result["run_all"] is False + assert "falcon" in result["affected"] + # _attention.py dependents should also be included + assert len(result["affected"]) > 2 + + def test_traceable_overridden_by_shared_infra(self): + """If both traceable and shared_infra change, run_all wins.""" + result = detect_affected_models( + [ + "src/mobius/components/_attention.py", + "src/mobius/_configs.py", + ] + ) + assert result["run_all"] is True + + def test_deleted_traceable_file_triggers_run_all(self): + """A deleted component file triggers run_all (conservative).""" + result = detect_affected_models( + ["src/mobius/components/_nonexistent_component.py"] + ) + assert result["run_all"] is True + + +# ---------------------------------------------------------------- +# Traceable tracing integration tests +# ---------------------------------------------------------------- + + +class TestTraceableTracing: + """Verify the import graph tracing for component/task files.""" + + def test_attention_component_finds_model_dependents(self): + """_attention.py should trace to models that import it.""" + import_graph = _build_import_graph(_SRC_ROOT) + registry_map = _build_source_module_to_types() + + dependents = _find_reverse_dependents( + "mobius.components._attention", import_graph + ) + # At minimum, models that use Attention should appear + affected_types: set[str] = set() + for dep in dependents: + if dep in registry_map: + affected_types.update(registry_map[dep]) + assert len(affected_types) > 0, ( + "Expected _attention.py to affect at least one model" + ) + + def test_traceable_result_is_subset_of_all_models(self): + """Traceable tracing should return a subset, not all models.""" + # A niche component should affect fewer models than _common.py + result_common = detect_affected_models( + ["src/mobius/components/_common.py"] + ) + result_niche = detect_affected_models( + ["src/mobius/components/_sam_vision.py"] + ) + assert result_common["run_all"] is False + assert result_niche["run_all"] is False + # Niche component should affect fewer models + assert len(result_niche["affected"]) <= len( + result_common["affected"] + ), ( + f"_sam_vision.py ({len(result_niche['affected'])} models) should " + f"affect <= models than _common.py ({len(result_common['affected'])})" + ) + # ---------------------------------------------------------------- # CLI tests From 730984535619e1ec491ba501be2f55014ee5c9c1 Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Tue, 14 Apr 2026 22:39:54 +0000 Subject: [PATCH 4/9] refactor: Remove unused _SHARED_INFRA_PREFIXES variable from detection script --- scripts/detect_affected_models.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/detect_affected_models.py b/scripts/detect_affected_models.py index 1f26b044..5dd7fb79 100644 --- a/scripts/detect_affected_models.py +++ b/scripts/detect_affected_models.py @@ -48,8 +48,6 @@ "src/mobius/models/__init__.py", ) -_SHARED_INFRA_PREFIXES: tuple[str, ...] = () - # Traceable infrastructure: component/task files that are analyzed via the # import graph to find which models they actually affect, rather than # triggering run_all unconditionally. @@ -83,9 +81,6 @@ def classify_file(path: str) -> str: # Shared infrastructure patterns if normalized in _SHARED_INFRA_PATTERNS: return "shared_infra" - for prefix in _SHARED_INFRA_PREFIXES: - if normalized.startswith(prefix): - return "shared_infra" # Traceable infrastructure (components, tasks) — traced via import graph for prefix in _TRACEABLE_PREFIXES: From 7d00aee2e125ad477b911ce532d3ed47fdd9f6fb Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Wed, 15 Apr 2026 16:53:50 +0000 Subject: [PATCH 5/9] fix: Update classification logic for task files to trigger run_all --- scripts/detect_affected_models.py | 15 ++++++++++++--- scripts/detect_affected_models_test.py | 10 ++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/scripts/detect_affected_models.py b/scripts/detect_affected_models.py index 5dd7fb79..e5f637b9 100644 --- a/scripts/detect_affected_models.py +++ b/scripts/detect_affected_models.py @@ -48,12 +48,18 @@ "src/mobius/models/__init__.py", ) -# Traceable infrastructure: component/task files that are analyzed via the +# Task files are resolved by string-based lookup at runtime (not Python +# imports), so the import graph cannot trace task → model dependencies. +# Keep tasks/ as shared_infra until a task→model_type mapping exists. +_SHARED_INFRA_PREFIXES = ( + "src/mobius/tasks/", +) + +# Traceable infrastructure: component files that are analyzed via the # import graph to find which models they actually affect, rather than # triggering run_all unconditionally. _TRACEABLE_PREFIXES = ( "src/mobius/components/", - "src/mobius/tasks/", ) @@ -81,8 +87,11 @@ def classify_file(path: str) -> str: # Shared infrastructure patterns if normalized in _SHARED_INFRA_PATTERNS: return "shared_infra" + for prefix in _SHARED_INFRA_PREFIXES: + if normalized.startswith(prefix): + return "shared_infra" - # Traceable infrastructure (components, tasks) — traced via import graph + # Traceable infrastructure (components) — traced via import graph for prefix in _TRACEABLE_PREFIXES: if normalized.startswith(prefix): return "traceable" diff --git a/scripts/detect_affected_models_test.py b/scripts/detect_affected_models_test.py index 155d5dcb..6eafbc14 100644 --- a/scripts/detect_affected_models_test.py +++ b/scripts/detect_affected_models_test.py @@ -46,7 +46,7 @@ def test_component_file(self): assert classify_file("src/mobius/components/_attention.py") == "traceable" def test_task_file(self): - assert classify_file("src/mobius/tasks/_causal_lm.py") == "traceable" + assert classify_file("src/mobius/tasks/_causal_lm.py") == "shared_infra" def test_configs_file(self): assert classify_file("src/mobius/_configs.py") == "shared_infra" @@ -181,12 +181,10 @@ def test_component_change_traces_affected_models(self): # _attention.py is imported by many models — should find affected types assert len(result["affected"]) > 0 - def test_task_change_traces_affected_models(self): - """A task change traces through the import graph to find affected models.""" + def test_task_change_triggers_run_all(self): + """Task files use string-based lookup, not imports — must trigger run_all.""" result = detect_affected_models(["src/mobius/tasks/_causal_lm.py"]) - assert result["run_all"] is False - # _causal_lm.py is imported by task infrastructure — may affect models - # The key point: it does NOT trigger run_all + assert result["run_all"] is True def test_configs_change_triggers_run_all(self): result = detect_affected_models(["src/mobius/_configs.py"]) From d7bf992ed8afc574f86e204b823f35764af013c7 Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Wed, 15 Apr 2026 16:57:57 +0000 Subject: [PATCH 6/9] fix: Handle __init__.py files correctly in module name conversion --- scripts/detect_affected_models.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/detect_affected_models.py b/scripts/detect_affected_models.py index e5f637b9..6b07c3df 100644 --- a/scripts/detect_affected_models.py +++ b/scripts/detect_affected_models.py @@ -148,6 +148,9 @@ def _module_name_from_path(filepath: Path) -> str | None: return None parts = list(rel.with_suffix("").parts) + # __init__.py represents the package itself, not a submodule + if parts and parts[-1] == "__init__": + parts = parts[:-1] return ".".join(parts) @@ -161,8 +164,6 @@ def _build_import_graph( """ graph: dict[str, set[str]] = {} for pyfile in search_dir.rglob("*.py"): - if pyfile.name.startswith("__"): - continue if pyfile.name.endswith("_test.py"): continue mod_name = _module_name_from_path(pyfile) @@ -511,8 +512,12 @@ def detect_affected_models( normalized = path.replace("\\", "/") # Convert path to module name: src/mobius/components/_attention.py # → mobius.components._attention + # Special case: __init__.py → package name (mobius.components) rel = normalized[len("src/") :] - module_name = rel[:-3].replace("/", ".") # strip .py, dots + if rel.endswith("/__init__.py"): + module_name = rel[: -len("/__init__.py")].replace("/", ".") + else: + module_name = rel[:-3].replace("/", ".") # strip .py if not module_name: continue From aaa7ed995fbf93095693963d3f435dd9fac384bf Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Wed, 15 Apr 2026 17:01:09 +0000 Subject: [PATCH 7/9] fix: Correctly classify test files in the classify_file function --- scripts/detect_affected_models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/detect_affected_models.py b/scripts/detect_affected_models.py index 6b07c3df..b68888f0 100644 --- a/scripts/detect_affected_models.py +++ b/scripts/detect_affected_models.py @@ -84,6 +84,10 @@ def classify_file(path: str) -> str: rel = normalized[len("src/mobius/") :] + # Test files within the source tree (check before infra prefixes) + if rel.endswith("_test.py"): + return "test" + # Shared infrastructure patterns if normalized in _SHARED_INFRA_PATTERNS: return "shared_infra" @@ -100,10 +104,6 @@ def classify_file(path: str) -> str: if rel.startswith("models/") and not rel.endswith("_test.py"): return "model" - # Test files within the source tree - if rel.endswith("_test.py"): - return "test" - return "other" From 568ef65125f23196fa9bb8afd595dfd1ac764134 Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Wed, 15 Apr 2026 17:05:01 +0000 Subject: [PATCH 8/9] refactor: Simplify registry class mapping by removing unused patterns --- scripts/detect_affected_models.py | 105 ++---------------------------- 1 file changed, 4 insertions(+), 101 deletions(-) diff --git a/scripts/detect_affected_models.py b/scripts/detect_affected_models.py index b68888f0..f59bf05f 100644 --- a/scripts/detect_affected_models.py +++ b/scripts/detect_affected_models.py @@ -251,11 +251,9 @@ def _build_class_to_source_module() -> dict[str, str]: def _build_registry_class_to_types() -> dict[str, list[str]]: """Parse _registry.py to map class names to registered model_types. - Handles four patterns: - 1. Direct: reg.register("name", ClassName) - 2. For-loop: for name in (...): reg.register(name, ClassName) - 3. Dict-loop: for name, cls in {...}.items(): reg.register(name, cls) - 4. Declarative dict: _REGISTRATIONS = {"name": ModelRegistration(ClassName, ...)} + Parses the declarative ``_REGISTRATIONS`` dict:: + + _REGISTRATIONS = {"name": ModelRegistration(ClassName, ...)} """ registry_file = _SRC_ROOT / "_registry.py" class_to_types: dict[str, list[str]] = {} @@ -267,17 +265,7 @@ def _build_registry_class_to_types() -> dict[str, list[str]]: return class_to_types for node in ast.walk(tree): - # Pattern 1: Direct reg.register("name", ClassName) - if isinstance(node, ast.Call): - cls_name, arch_name = _match_register_call(node) - if cls_name and arch_name: - class_to_types.setdefault(cls_name, []).append(arch_name) - - # Pattern 2 & 3: For-loop with reg.register in body - if isinstance(node, ast.For): - _process_for_loop(node, class_to_types) - - # Pattern 4: _REGISTRATIONS = {"name": ModelRegistration(Cls, ...)} + # _REGISTRATIONS = {"name": ModelRegistration(Cls, ...)} # Handles both plain assignment and type-annotated assignment if isinstance(node, ast.Assign): for target in node.targets: @@ -295,80 +283,6 @@ def _build_registry_class_to_types() -> dict[str, list[str]]: return {c: sorted(set(t)) for c, t in class_to_types.items()} -def _match_register_call( - node: ast.Call, -) -> tuple[str | None, str | None]: - """Match a reg.register("name", ClassName) call. - - Returns (class_name, arch_name) or (None, None). - """ - func = node.func - if not ( - isinstance(func, ast.Attribute) - and func.attr == "register" - and isinstance(func.value, ast.Name) - and func.value.id == "reg" - ): - return None, None - if len(node.args) < 2: - return None, None - - name_node = node.args[0] - cls_node = node.args[1] - - if not (isinstance(name_node, ast.Constant) and isinstance(name_node.value, str)): - return None, None - if not isinstance(cls_node, ast.Name): - return None, None - - return cls_node.id, name_node.value - - -def _process_for_loop( - node: ast.For, - class_to_types: dict[str, list[str]], -) -> None: - """Extract model_type → class mappings from for-loop patterns.""" - # Pattern 2: for name in ("llama", "qwen2", ...): reg.register(name, Cls) - string_names = _extract_string_constants(node.iter) - if string_names: - for stmt in node.body: - if not isinstance(stmt, ast.Expr): - continue - call = stmt.value - if not isinstance(call, ast.Call): - continue - func = call.func - if not ( - isinstance(func, ast.Attribute) - and func.attr == "register" - and isinstance(func.value, ast.Name) - and func.value.id == "reg" - ): - continue - if len(call.args) >= 2 and isinstance(call.args[1], ast.Name): - cls_name = call.args[1].id - class_to_types.setdefault(cls_name, []).extend(string_names) - return - - # Pattern 3: for name, cls in {...}.items(): reg.register(name, cls) - iter_node = node.iter - if ( - isinstance(iter_node, ast.Call) - and isinstance(iter_node.func, ast.Attribute) - and iter_node.func.attr == "items" - and isinstance(iter_node.func.value, ast.Dict) - ): - dict_node = iter_node.func.value - for key, value in zip(dict_node.keys, dict_node.values): - if ( - isinstance(key, ast.Constant) - and isinstance(key.value, str) - and isinstance(value, ast.Name) - ): - class_to_types.setdefault(value.id, []).append(key.value) - - def _process_registrations_dict( dict_node: ast.Dict, class_to_types: dict[str, list[str]], @@ -385,17 +299,6 @@ def _process_registrations_dict( class_to_types.setdefault(cls_arg.id, []).append(arch_name) -def _extract_string_constants(node: ast.expr) -> list[str]: - """Extract string constants from a Tuple or List AST node.""" - if isinstance(node, (ast.Tuple, ast.List)): - result = [] - for elt in node.elts: - if isinstance(elt, ast.Constant) and isinstance(elt.value, str): - result.append(elt.value) - return result - return [] - - def _build_source_module_to_types() -> dict[str, list[str]]: """Build the final source_module → [model_types] mapping. From 55e25cb950d5ecd3481b474c7444306d9751b198 Mon Sep 17 00:00:00 2001 From: Tommaso Adani Date: Wed, 15 Apr 2026 17:27:03 +0000 Subject: [PATCH 9/9] lint: Fixed linting issues --- scripts/detect_affected_models.py | 8 ++----- scripts/detect_affected_models_test.py | 30 +++++++------------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/scripts/detect_affected_models.py b/scripts/detect_affected_models.py index f59bf05f..04706162 100644 --- a/scripts/detect_affected_models.py +++ b/scripts/detect_affected_models.py @@ -51,16 +51,12 @@ # Task files are resolved by string-based lookup at runtime (not Python # imports), so the import graph cannot trace task → model dependencies. # Keep tasks/ as shared_infra until a task→model_type mapping exists. -_SHARED_INFRA_PREFIXES = ( - "src/mobius/tasks/", -) +_SHARED_INFRA_PREFIXES = ("src/mobius/tasks/",) # Traceable infrastructure: component files that are analyzed via the # import graph to find which models they actually affect, rather than # triggering run_all unconditionally. -_TRACEABLE_PREFIXES = ( - "src/mobius/components/", -) +_TRACEABLE_PREFIXES = ("src/mobius/components/",) def classify_file(path: str) -> str: diff --git a/scripts/detect_affected_models_test.py b/scripts/detect_affected_models_test.py index 6eafbc14..f9e07237 100644 --- a/scripts/detect_affected_models_test.py +++ b/scripts/detect_affected_models_test.py @@ -19,12 +19,12 @@ sys.path.insert(0, str(_SCRIPTS_DIR)) from detect_affected_models import ( # noqa: E402 + _SRC_ROOT, _build_class_to_source_module, _build_import_graph, _build_registry_class_to_types, _build_source_module_to_types, _find_reverse_dependents, - _SRC_ROOT, classify_file, detect_affected_models, ) @@ -287,9 +287,7 @@ def test_shared_infra_still_triggers_run_all(self): "tests/_test_configs.py", ]: result = detect_affected_models([path]) - assert result["run_all"] is True, ( - f"{path} should trigger run_all but didn't" - ) + assert result["run_all"] is True, f"{path} should trigger run_all but didn't" def test_traceable_and_model_combined(self): """A component + model file change returns union of affected types.""" @@ -316,9 +314,7 @@ def test_traceable_overridden_by_shared_infra(self): def test_deleted_traceable_file_triggers_run_all(self): """A deleted component file triggers run_all (conservative).""" - result = detect_affected_models( - ["src/mobius/components/_nonexistent_component.py"] - ) + result = detect_affected_models(["src/mobius/components/_nonexistent_component.py"]) assert result["run_all"] is True @@ -335,33 +331,23 @@ def test_attention_component_finds_model_dependents(self): import_graph = _build_import_graph(_SRC_ROOT) registry_map = _build_source_module_to_types() - dependents = _find_reverse_dependents( - "mobius.components._attention", import_graph - ) + dependents = _find_reverse_dependents("mobius.components._attention", import_graph) # At minimum, models that use Attention should appear affected_types: set[str] = set() for dep in dependents: if dep in registry_map: affected_types.update(registry_map[dep]) - assert len(affected_types) > 0, ( - "Expected _attention.py to affect at least one model" - ) + assert len(affected_types) > 0, "Expected _attention.py to affect at least one model" def test_traceable_result_is_subset_of_all_models(self): """Traceable tracing should return a subset, not all models.""" # A niche component should affect fewer models than _common.py - result_common = detect_affected_models( - ["src/mobius/components/_common.py"] - ) - result_niche = detect_affected_models( - ["src/mobius/components/_sam_vision.py"] - ) + result_common = detect_affected_models(["src/mobius/components/_common.py"]) + result_niche = detect_affected_models(["src/mobius/components/_sam_vision.py"]) assert result_common["run_all"] is False assert result_niche["run_all"] is False # Niche component should affect fewer models - assert len(result_niche["affected"]) <= len( - result_common["affected"] - ), ( + assert len(result_niche["affected"]) <= len(result_common["affected"]), ( f"_sam_vision.py ({len(result_niche['affected'])} models) should " f"affect <= models than _common.py ({len(result_common['affected'])})" )