Visitar URL original
Merge branch 'main' into cy/analyze-vids-gui-fixes · DeepLabCut/DeepLabCut@ef05ba3 · GitHub
Skip to content

Commit ef05ba3

Browse files
authored
Merge branch 'main' into cy/analyze-vids-gui-fixes
2 parents d795bab + 6ddb88f commit ef05ba3

5 files changed

Lines changed: 190 additions & 61 deletions

File tree

‎deeplabcut/gui/components.py‎

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -292,17 +292,11 @@ def get_effective_videotype(
292292
return f".{videotype}"
293293
return videotype
294294

295-
def get_files_grouped_by_suffix(
296-
self,
297-
keep_dot: bool = False,
298-
) -> dict[str, list[Path]]:
299-
"""Return selected files grouped by suffix."""
295+
def get_files_grouped_by_suffix(self, keep_dot: bool = False) -> dict[str, list[Path]]:
296+
"""Return a dict grouping selected files by their suffixes."""
300297
groups: dict[str, list[Path]] = {}
301-
302-
for video in self.files:
303-
path = Path(video)
304-
suffix = path.suffix.lower()
305-
298+
for f in self.files:
299+
suffix = f.suffix.lower()
306300
if not keep_dot:
307301
suffix = suffix.lstrip(".")
308302

‎tests/tools/test_selector/test_selector_decision.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import pytest
77
from pydantic import ValidationError
88

9+
from tools import test_selector
10+
from tools.test_selector import decide, order_functional_scripts
911
from tools.test_selector_config import (
1012
CATEGORY_RULES,
1113
CategoryRule,
@@ -339,3 +341,54 @@ def test_current_selected_paths_exist():
339341
missing.append((rule.name, "script", path))
340342

341343
assert missing == []
344+
345+
346+
def test_3d_script_includes_tensorflow_prerequisite():
347+
scripts = order_functional_scripts({"examples/testscript_3d.py"})
348+
349+
assert scripts == [
350+
"examples/testscript_tensorflow_single_animal.py",
351+
"examples/testscript_3d.py",
352+
]
353+
354+
355+
def test_explicit_dependency_is_not_duplicated():
356+
scripts = order_functional_scripts(
357+
{
358+
"examples/testscript_tensorflow_single_animal.py",
359+
"examples/testscript_3d.py",
360+
}
361+
)
362+
363+
assert scripts == [
364+
"examples/testscript_tensorflow_single_animal.py",
365+
"examples/testscript_3d.py",
366+
]
367+
368+
369+
def test_functional_script_dependency_cycle(monkeypatch):
370+
monkeypatch.setattr(
371+
test_selector,
372+
"FUNC_SCRIPT_DEPENDENCIES",
373+
{
374+
"a.py": ("b.py",),
375+
"b.py": ("a.py",),
376+
},
377+
)
378+
379+
with pytest.raises(ValueError, match="Cyclic"):
380+
order_functional_scripts({"a.py"})
381+
382+
383+
def test_3d_changes_select_scripts_in_dependency_order():
384+
result = decide(["deeplabcut/pose_estimation_3d/plotting3D.py"])
385+
386+
assert result.functional_scripts == [
387+
"examples/testscript_tensorflow_single_animal.py",
388+
"examples/testscript_3d.py",
389+
]
390+
assert (
391+
"dependency:examples/testscript_3d.py"
392+
in result.provenance.scripts["examples/testscript_tensorflow_single_animal.py"]
393+
)
394+
assert "3d_pose_estimation" in result.provenance.scripts["examples/testscript_3d.py"]

‎tools/test_selector.py‎

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
CATEGORY_RULE_BY_NAME,
6666
CATEGORY_RULES,
6767
FULL_SUITE_TRIGGERS,
68+
FUNC_SCRIPT_DEPENDENCIES,
6869
LINT_ONLY_FILES,
6970
MINIMAL_PYTEST,
7071
)
@@ -75,6 +76,7 @@
7576
CATEGORY_RULE_BY_NAME,
7677
CATEGORY_RULES,
7778
FULL_SUITE_TRIGGERS,
79+
FUNC_SCRIPT_DEPENDENCIES,
7880
LINT_ONLY_FILES,
7981
MINIMAL_PYTEST,
8082
)
@@ -332,6 +334,82 @@ def _matches_any(path: str, preds: Sequence[Callable[[str], bool]]) -> bool:
332334
return False
333335

334336

337+
def resolve_functional_scripts(
338+
selected: set[str],
339+
) -> tuple[list[str], dict[str, set[str]]]:
340+
"""Expand and dependency-order functional scripts.
341+
342+
Returns:
343+
ordered
344+
Selected scripts and their transitive dependencies, with each
345+
dependency appearing before scripts that depend on it.
346+
dependency_sources
347+
Provenance entries for every dependency added during expansion.
348+
"""
349+
ordered: list[str] = []
350+
dependency_sources: dict[str, set[str]] = defaultdict(set)
351+
visited: set[str] = set()
352+
visiting: set[str] = set()
353+
354+
def visit(script: str) -> None:
355+
if script in visited:
356+
return
357+
358+
if script in visiting:
359+
raise ValueError(f"Cyclic functional-script dependency involving {script!r}")
360+
361+
visiting.add(script)
362+
363+
for dependency in FUNC_SCRIPT_DEPENDENCIES.get(
364+
script,
365+
(),
366+
):
367+
dependency_sources[dependency].add(f"dependency:{script}")
368+
visit(dependency)
369+
370+
visiting.remove(script)
371+
visited.add(script)
372+
ordered.append(script)
373+
374+
for script in sorted(selected):
375+
visit(script)
376+
377+
return ordered, dependency_sources
378+
379+
380+
def order_functional_scripts(
381+
selected: set[str],
382+
) -> list[str]:
383+
"""Expand and order functional scripts after their dependencies."""
384+
ordered: list[str] = []
385+
visited: set[str] = set()
386+
visiting: set[str] = set()
387+
388+
def visit(script: str) -> None:
389+
if script in visited:
390+
return
391+
392+
if script in visiting:
393+
raise ValueError(f"Cyclic functional-script dependency involving {script!r}")
394+
395+
visiting.add(script)
396+
397+
for dependency in FUNC_SCRIPT_DEPENDENCIES.get(
398+
script,
399+
(),
400+
):
401+
visit(dependency)
402+
403+
visiting.remove(script)
404+
visited.add(script)
405+
ordered.append(script)
406+
407+
for script in sorted(selected):
408+
visit(script)
409+
410+
return ordered
411+
412+
335413
def decide(files: list[str]) -> SelectorResult:
336414
reasons: list[str] = []
337415
lane_reasons: dict[str, list[str]] = {}
@@ -499,13 +577,17 @@ def decide(files: list[str]) -> SelectorResult:
499577
fast_reasons.append("fallback_minimal_pytest")
500578
lane_reasons["fast"] = fast_reasons
501579

580+
ordered_functional_scripts, dependency_sources = resolve_functional_scripts(functional_set)
581+
for script, srcs in dependency_sources.items():
582+
script_sources[script].update(srcs)
583+
502584
return SelectorResult(
503585
lanes=lanes,
504586
pytest_paths=sorted(pytest_paths_set),
505-
functional_scripts=sorted(functional_set),
587+
functional_scripts=ordered_functional_scripts,
506588
provenance=SelectionProvenance(
507-
pytest={k: sorted(v) for k, v in sorted(pytest_sources.items())},
508-
scripts={k: sorted(v) for k, v in sorted(script_sources.items())},
589+
pytest={path: sorted(sources) for path, sources in sorted(pytest_sources.items())},
590+
scripts={path: sorted(sources) for path, sources in sorted(script_sources.items())},
509591
),
510592
reasons=reasons,
511593
changed_files=files,

‎tools/test_selector_config.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
PathPred = Callable[[str], bool]
1212

13+
FUNC_SCRIPT_DEPENDENCIES: dict[str, tuple[str, ...]] = {
14+
"examples/testscript_3d.py": ("examples/testscript_tensorflow_single_animal.py",),
15+
}
16+
1317

1418
def prefix(*values: str) -> PathPred:
1519
"""Match if path starts with any of the given prefixes."""

0 commit comments

Comments
 (0)