Skip to content

Commit 2d01f8c

Browse files
authored
fix: only trigger script with CODE label (#3707)
This PR is inspired by #3706, resolves issues #3456 and #3474, and addresses the concern raised in #3148 (comment) In brief, commit e502312 made changes so that the modification time (mtime) of script and notebook files no longer triggers a rerun of the workflow, later, commit e8a0b83 reversed this, making mtime changes always trigger reruns -- even when RerunTrigger.CODE is not specified in `--rerun-triggers`. This created an undesirable situation: legitimate and common scenarios (e.g., copying scripts or pulling them from a Git repository) could unnecessarily trigger reruns — as reported in #3474. To make the behavior more intuitive, the `outputs_older_than_script_or_notebook` condition should take precedence over metadata checks. However, this condition should still be governed by the presence of `RerunTrigger.CODE`. ### QC <!-- Make sure that you can tick the boxes below. --> * [x] The PR contains a test case for the changes or the changes are already covered by an existing test case. * [ ] The documentation (`docs/`) is updated to reflect the changes or this is not necessary (e.g. if the change does neither modify the language nor the behavior or functionalities of Snakemake). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Code-change reruns now only run when the CODE rerun trigger is enabled; code-change detection correctly accumulates signals from multiple sources to avoid missed or spurious reruns. * **Tests** * Added an MTIME-triggered rerun test validating that outputs retain timestamps when only input mtimes change. * **Chores** * Fixed a test import path and broadened connectivity error handling to include timeouts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 8cb7d8c commit 2d01f8c

File tree

3 files changed

+32
-12
lines changed

3 files changed

+32
-12
lines changed

src/snakemake/dag.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,15 +1452,15 @@ async def updated_input():
14521452
# for determining any other changes than file modification dates, as it will
14531453
# change after evaluating the input function of the job in the second pass.
14541454

1455-
# The list comprehension is needed below in order to
1456-
# collect all the async generator items before
1457-
# applying any().
1458-
reason.code_changed = any(
1459-
[
1460-
f
1461-
async for f in job.outputs_older_than_script_or_notebook()
1462-
]
1463-
)
1455+
if RerunTrigger.CODE in self.workflow.rerun_triggers:
1456+
# Always check script/notebook mtime unless CODE trigger is disabled.
1457+
# Short-circuit on first older output to avoid building a list.
1458+
reason.code_changed = False
1459+
async for (
1460+
_
1461+
) in job.outputs_older_than_script_or_notebook():
1462+
reason.code_changed = True
1463+
break
14641464
if not self.workflow.persistence.has_metadata(job):
14651465
reason.no_metadata = True
14661466
elif self.workflow.persistence.has_outdated_metadata(job):

tests/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from os.path import join
1414
import tempfile
1515
import hashlib
16-
import urllib
16+
import urllib.request
1717
import pytest
1818
import glob
1919
import subprocess
@@ -50,7 +50,7 @@ def is_connected():
5050
try:
5151
urllib.request.urlopen("http://www.google.com", timeout=1)
5252
return True
53-
except urllib.request.URLError:
53+
except (urllib.request.URLError, TimeoutError):
5454
return False
5555

5656

tests/tests.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,27 @@ def test_empty_include():
498498

499499

500500
def test_script_python():
501-
run(dpath("test_script_py"))
501+
tmpdir = run(dpath("test_script_py"), cleanup=False)
502+
# update timestamp of script
503+
os.utime(os.path.join(tmpdir, "scripts/test_explicit_import.py"))
504+
outfile_path = os.path.join(tmpdir, "explicit_import.py.out")
505+
outfile_timestamp_orig = os.path.getmtime(outfile_path)
506+
# won't update output without trigger CODE
507+
run(
508+
dpath("test_script_py"),
509+
cleanup=False,
510+
tmpdir=tmpdir,
511+
shellcmd="snakemake -c1 --rerun-triggers mtime",
512+
)
513+
assert outfile_timestamp_orig == os.path.getmtime(outfile_path)
514+
run(
515+
dpath("test_script_py"),
516+
cleanup=False,
517+
tmpdir=tmpdir,
518+
shellcmd="snakemake -c1",
519+
)
520+
assert outfile_timestamp_orig != os.path.getmtime(outfile_path)
521+
# shutil.rmtree(tmpdir, ignore_errors=ON_WINDOWS)
502522

503523

504524
@skip_on_windows # Test relies on perl

0 commit comments

Comments
 (0)