tasktree 0.0.19__py3-none-any.whl → 0.0.21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tasktree/executor.py CHANGED
@@ -14,7 +14,7 @@ from pathlib import Path
14
14
  from typing import Any
15
15
 
16
16
  from tasktree import docker as docker_module
17
- from tasktree.graph import get_implicit_inputs, resolve_execution_order, resolve_dependency_output_references
17
+ from tasktree.graph import get_implicit_inputs, resolve_execution_order, resolve_dependency_output_references, resolve_self_references
18
18
  from tasktree.hasher import hash_args, hash_task, make_cache_key
19
19
  from tasktree.parser import Recipe, Task, Environment
20
20
  from tasktree.state import StateManager, TaskState
@@ -22,7 +22,10 @@ from tasktree.state import StateManager, TaskState
22
22
 
23
23
  @dataclass
24
24
  class TaskStatus:
25
- """Status of a task for execution planning."""
25
+ """
26
+ Status of a task for execution planning.
27
+ @athena: a718e784981d
28
+ """
26
29
 
27
30
  task_name: str
28
31
  will_run: bool
@@ -33,13 +36,19 @@ class TaskStatus:
33
36
 
34
37
 
35
38
  class ExecutionError(Exception):
36
- """Raised when task execution fails."""
39
+ """
40
+ Raised when task execution fails.
41
+ @athena: f22d72903ee4
42
+ """
37
43
 
38
44
  pass
39
45
 
40
46
 
41
47
  class Executor:
42
- """Executes tasks with incremental execution logic."""
48
+ """
49
+ Executes tasks with incremental execution logic.
50
+ @athena: ac1e2fc7b82b
51
+ """
43
52
 
44
53
  # Protected environment variables that cannot be overridden by exported args
45
54
  PROTECTED_ENV_VARS = {
@@ -54,24 +63,28 @@ class Executor:
54
63
  }
55
64
 
56
65
  def __init__(self, recipe: Recipe, state_manager: StateManager):
57
- """Initialize executor.
66
+ """
67
+ Initialize executor.
58
68
 
59
69
  Args:
60
- recipe: Parsed recipe containing all tasks
61
- state_manager: State manager for tracking task execution
70
+ recipe: Parsed recipe containing all tasks
71
+ state_manager: State manager for tracking task execution
72
+ @athena: 21b65db48bca
62
73
  """
63
74
  self.recipe = recipe
64
75
  self.state = state_manager
65
76
  self.docker_manager = docker_module.DockerManager(recipe.project_root)
66
77
 
67
78
  def _has_regular_args(self, task: Task) -> bool:
68
- """Check if a task has any regular (non-exported) arguments.
79
+ """
80
+ Check if a task has any regular (non-exported) arguments.
69
81
 
70
82
  Args:
71
- task: Task to check
83
+ task: Task to check
72
84
 
73
85
  Returns:
74
- True if task has at least one regular (non-exported) argument, False otherwise
86
+ True if task has at least one regular (non-exported) argument, False otherwise
87
+ @athena: 0fc46146eed3
75
88
  """
76
89
  if not task.args:
77
90
  return False
@@ -93,14 +106,16 @@ class Executor:
93
106
  return False
94
107
 
95
108
  def _filter_regular_args(self, task: Task, task_args: dict[str, Any]) -> dict[str, Any]:
96
- """Filter task_args to only include regular (non-exported) arguments.
109
+ """
110
+ Filter task_args to only include regular (non-exported) arguments.
97
111
 
98
112
  Args:
99
- task: Task definition
100
- task_args: Dictionary of all task arguments
113
+ task: Task definition
114
+ task_args: Dictionary of all task arguments
101
115
 
102
116
  Returns:
103
- Dictionary containing only regular (non-exported) arguments
117
+ Dictionary containing only regular (non-exported) arguments
118
+ @athena: 811abd3a56f9
104
119
  """
105
120
  if not task.args or not task_args:
106
121
  return {}
@@ -121,19 +136,21 @@ class Executor:
121
136
  return {k: v for k, v in task_args.items() if k not in exported_names}
122
137
 
123
138
  def _collect_early_builtin_variables(self, task: Task, timestamp: datetime) -> dict[str, str]:
124
- """Collect built-in variables that don't depend on working_dir.
139
+ """
140
+ Collect built-in variables that don't depend on working_dir.
125
141
 
126
142
  These variables can be used in the working_dir field itself.
127
143
 
128
144
  Args:
129
- task: Task being executed
130
- timestamp: Timestamp when task started execution
145
+ task: Task being executed
146
+ timestamp: Timestamp when task started execution
131
147
 
132
148
  Returns:
133
- Dictionary mapping built-in variable names to their string values
149
+ Dictionary mapping built-in variable names to their string values
134
150
 
135
151
  Raises:
136
- ExecutionError: If any built-in variable fails to resolve
152
+ ExecutionError: If any built-in variable fails to resolve
153
+ @athena: 0b348e67ce4c
137
154
  """
138
155
  import os
139
156
 
@@ -174,18 +191,20 @@ class Executor:
174
191
  return builtin_vars
175
192
 
176
193
  def _collect_builtin_variables(self, task: Task, working_dir: Path, timestamp: datetime) -> dict[str, str]:
177
- """Collect built-in variables for task execution.
194
+ """
195
+ Collect built-in variables for task execution.
178
196
 
179
197
  Args:
180
- task: Task being executed
181
- working_dir: Resolved working directory for the task
182
- timestamp: Timestamp when task started execution
198
+ task: Task being executed
199
+ working_dir: Resolved working directory for the task
200
+ timestamp: Timestamp when task started execution
183
201
 
184
202
  Returns:
185
- Dictionary mapping built-in variable names to their string values
203
+ Dictionary mapping built-in variable names to their string values
186
204
 
187
205
  Raises:
188
- ExecutionError: If any built-in variable fails to resolve
206
+ ExecutionError: If any built-in variable fails to resolve
207
+ @athena: bb8c385cb0a5
189
208
  """
190
209
  # Get early builtin vars (those that don't depend on working_dir)
191
210
  builtin_vars = self._collect_early_builtin_variables(task, timestamp)
@@ -197,16 +216,18 @@ class Executor:
197
216
  return builtin_vars
198
217
 
199
218
  def _prepare_env_with_exports(self, exported_env_vars: dict[str, str] | None = None) -> dict[str, str]:
200
- """Prepare environment with exported arguments.
219
+ """
220
+ Prepare environment with exported arguments.
201
221
 
202
222
  Args:
203
- exported_env_vars: Exported arguments to set as environment variables
223
+ exported_env_vars: Exported arguments to set as environment variables
204
224
 
205
225
  Returns:
206
- Environment dict with exported args merged
226
+ Environment dict with exported args merged
207
227
 
208
228
  Raises:
209
- ValueError: If an exported arg attempts to override a protected environment variable
229
+ ValueError: If an exported arg attempts to override a protected environment variable
230
+ @athena: 5340be771194
210
231
  """
211
232
  env = os.environ.copy()
212
233
  if exported_env_vars:
@@ -221,10 +242,12 @@ class Executor:
221
242
  return env
222
243
 
223
244
  def _get_platform_default_environment(self) -> tuple[str, list[str]]:
224
- """Get default shell and args for current platform.
245
+ """
246
+ Get default shell and args for current platform.
225
247
 
226
248
  Returns:
227
- Tuple of (shell, args) for platform default
249
+ Tuple of (shell, args) for platform default
250
+ @athena: b67799671787
228
251
  """
229
252
  is_windows = platform.system() == "Windows"
230
253
  if is_windows:
@@ -233,7 +256,8 @@ class Executor:
233
256
  return ("bash", ["-c"])
234
257
 
235
258
  def _get_effective_env_name(self, task: Task) -> str:
236
- """Get the effective environment name for a task.
259
+ """
260
+ Get the effective environment name for a task.
237
261
 
238
262
  Resolution order:
239
263
  1. Recipe's global_env_override (from CLI --env)
@@ -242,10 +266,11 @@ class Executor:
242
266
  4. Empty string (for platform default)
243
267
 
244
268
  Args:
245
- task: Task to get environment name for
269
+ task: Task to get environment name for
246
270
 
247
271
  Returns:
248
- Environment name (empty string if using platform default)
272
+ Environment name (empty string if using platform default)
273
+ @athena: e5bface8a3a2
249
274
  """
250
275
  # Check for global override first
251
276
  if self.recipe.global_env_override:
@@ -263,7 +288,8 @@ class Executor:
263
288
  return ""
264
289
 
265
290
  def _resolve_environment(self, task: Task) -> tuple[str, list[str], str]:
266
- """Resolve which environment to use for a task.
291
+ """
292
+ Resolve which environment to use for a task.
267
293
 
268
294
  Resolution order:
269
295
  1. Recipe's global_env_override (from CLI --env)
@@ -272,10 +298,11 @@ class Executor:
272
298
  4. Platform default (bash on Unix, cmd on Windows)
273
299
 
274
300
  Args:
275
- task: Task to resolve environment for
301
+ task: Task to resolve environment for
276
302
 
277
303
  Returns:
278
- Tuple of (shell, args, preamble)
304
+ Tuple of (shell, args, preamble)
305
+ @athena: b919568f73fc
279
306
  """
280
307
  # Check for global override first
281
308
  env_name = self.recipe.global_env_override
@@ -305,7 +332,8 @@ class Executor:
305
332
  args_dict: dict[str, Any],
306
333
  force: bool = False,
307
334
  ) -> TaskStatus:
308
- """Check if a task needs to run.
335
+ """
336
+ Check if a task needs to run.
309
337
 
310
338
  A task executes if ANY of these conditions are met:
311
339
  1. Force flag is set (--force)
@@ -318,12 +346,13 @@ class Executor:
318
346
  8. Different arguments than any cached execution
319
347
 
320
348
  Args:
321
- task: Task to check
322
- args_dict: Arguments for this task execution
323
- force: If True, ignore freshness and force execution
349
+ task: Task to check
350
+ args_dict: Arguments for this task execution
351
+ force: If True, ignore freshness and force execution
324
352
 
325
353
  Returns:
326
- TaskStatus indicating whether task will run and why
354
+ TaskStatus indicating whether task will run and why
355
+ @athena: 7252f5db8a4d
327
356
  """
328
357
  # If force flag is set, always run
329
358
  if force:
@@ -404,19 +433,21 @@ class Executor:
404
433
  force: bool = False,
405
434
  only: bool = False,
406
435
  ) -> dict[str, TaskStatus]:
407
- """Execute a task and its dependencies.
436
+ """
437
+ Execute a task and its dependencies.
408
438
 
409
439
  Args:
410
- task_name: Name of task to execute
411
- args_dict: Arguments to pass to the task
412
- force: If True, ignore freshness and re-run all tasks
413
- only: If True, run only the specified task without dependencies (implies force=True)
440
+ task_name: Name of task to execute
441
+ args_dict: Arguments to pass to the task
442
+ force: If True, ignore freshness and re-run all tasks
443
+ only: If True, run only the specified task without dependencies (implies force=True)
414
444
 
415
445
  Returns:
416
- Dictionary of task names to their execution status
446
+ Dictionary of task names to their execution status
417
447
 
418
448
  Raises:
419
- ExecutionError: If task execution fails
449
+ ExecutionError: If task execution fails
450
+ @athena: 1c293ee6a6fa
420
451
  """
421
452
  if args_dict is None:
422
453
  args_dict = {}
@@ -437,6 +468,10 @@ class Executor:
437
468
  # This substitutes {{ dep.*.outputs.* }} templates before execution
438
469
  resolve_dependency_output_references(self.recipe, execution_order)
439
470
 
471
+ # Resolve self-references in topological order
472
+ # This substitutes {{ self.inputs.* }} and {{ self.outputs.* }} templates
473
+ resolve_self_references(self.recipe, execution_order)
474
+
440
475
  # Single phase: Check and execute incrementally
441
476
  statuses: dict[str, TaskStatus] = {}
442
477
  for name, task_args in execution_order:
@@ -481,14 +516,16 @@ class Executor:
481
516
  return statuses
482
517
 
483
518
  def _run_task(self, task: Task, args_dict: dict[str, Any]) -> None:
484
- """Execute a single task.
519
+ """
520
+ Execute a single task.
485
521
 
486
522
  Args:
487
- task: Task to execute
488
- args_dict: Arguments to substitute in command
523
+ task: Task to execute
524
+ args_dict: Arguments to substitute in command
489
525
 
490
526
  Raises:
491
- ExecutionError: If task execution fails
527
+ ExecutionError: If task execution fails
528
+ @athena: 885c66658550
492
529
  """
493
530
  # Capture timestamp at task start for consistency (in UTC)
494
531
  task_start_time = datetime.now(timezone.utc)
@@ -562,18 +599,20 @@ class Executor:
562
599
  self, cmd: str, working_dir: Path, task_name: str, shell: str, shell_args: list[str],
563
600
  exported_env_vars: dict[str, str] | None = None
564
601
  ) -> None:
565
- """Execute a single-line command via shell.
602
+ """
603
+ Execute a single-line command via shell.
566
604
 
567
605
  Args:
568
- cmd: Command string
569
- working_dir: Working directory
570
- task_name: Task name (for error messages)
571
- shell: Shell executable to use
572
- shell_args: Arguments to pass to shell
573
- exported_env_vars: Exported arguments to set as environment variables
606
+ cmd: Command string
607
+ working_dir: Working directory
608
+ task_name: Task name (for error messages)
609
+ shell: Shell executable to use
610
+ shell_args: Arguments to pass to shell
611
+ exported_env_vars: Exported arguments to set as environment variables
574
612
 
575
613
  Raises:
576
- ExecutionError: If command execution fails
614
+ ExecutionError: If command execution fails
615
+ @athena: 46849e6a0bbb
577
616
  """
578
617
  # Prepare environment with exported args
579
618
  env = self._prepare_env_with_exports(exported_env_vars)
@@ -597,18 +636,20 @@ class Executor:
597
636
  self, cmd: str, working_dir: Path, task_name: str, shell: str, preamble: str,
598
637
  exported_env_vars: dict[str, str] | None = None
599
638
  ) -> None:
600
- """Execute a multi-line command via temporary script file.
639
+ """
640
+ Execute a multi-line command via temporary script file.
601
641
 
602
642
  Args:
603
- cmd: Multi-line command string
604
- working_dir: Working directory
605
- task_name: Task name (for error messages)
606
- shell: Shell to use for script execution
607
- preamble: Preamble text to prepend to script
608
- exported_env_vars: Exported arguments to set as environment variables
643
+ cmd: Multi-line command string
644
+ working_dir: Working directory
645
+ task_name: Task name (for error messages)
646
+ shell: Shell to use for script execution
647
+ preamble: Preamble text to prepend to script
648
+ exported_env_vars: Exported arguments to set as environment variables
609
649
 
610
650
  Raises:
611
- ExecutionError: If command execution fails
651
+ ExecutionError: If command execution fails
652
+ @athena: 825892b6db05
612
653
  """
613
654
  # Prepare environment with exported args
614
655
  env = self._prepare_env_with_exports(exported_env_vars)
@@ -667,17 +708,19 @@ class Executor:
667
708
  pass # Ignore cleanup errors
668
709
 
669
710
  def _substitute_builtin_in_environment(self, env: Environment, builtin_vars: dict[str, str]) -> Environment:
670
- """Substitute builtin and environment variables in environment fields.
711
+ """
712
+ Substitute builtin and environment variables in environment fields.
671
713
 
672
714
  Args:
673
- env: Environment to process
674
- builtin_vars: Built-in variable values
715
+ env: Environment to process
716
+ builtin_vars: Built-in variable values
675
717
 
676
718
  Returns:
677
- New Environment with builtin and environment variables substituted
719
+ New Environment with builtin and environment variables substituted
678
720
 
679
721
  Raises:
680
- ValueError: If builtin variable or environment variable is not defined
722
+ ValueError: If builtin variable or environment variable is not defined
723
+ @athena: 21e2ccd27dbb
681
724
  """
682
725
  from dataclasses import replace
683
726
 
@@ -724,17 +767,19 @@ class Executor:
724
767
  self, task: Task, env: Any, cmd: str, working_dir: Path,
725
768
  exported_env_vars: dict[str, str] | None = None
726
769
  ) -> None:
727
- """Execute task inside Docker container.
770
+ """
771
+ Execute task inside Docker container.
728
772
 
729
773
  Args:
730
- task: Task to execute
731
- env: Docker environment configuration
732
- cmd: Command to execute
733
- working_dir: Host working directory
734
- exported_env_vars: Exported arguments to set as environment variables
774
+ task: Task to execute
775
+ env: Docker environment configuration
776
+ cmd: Command to execute
777
+ working_dir: Host working directory
778
+ exported_env_vars: Exported arguments to set as environment variables
735
779
 
736
780
  Raises:
737
- ExecutionError: If Docker execution fails
781
+ ExecutionError: If Docker execution fails
782
+ @athena: fe972e4c97a3
738
783
  """
739
784
  # Get builtin variables for substitution in environment fields
740
785
  task_start_time = datetime.now(timezone.utc)
@@ -776,15 +821,17 @@ class Executor:
776
821
  raise ExecutionError(str(e)) from e
777
822
 
778
823
  def _validate_no_working_dir_circular_ref(self, text: str) -> None:
779
- """Validate that working_dir field does not contain {{ tt.working_dir }}.
824
+ """
825
+ Validate that working_dir field does not contain {{ tt.working_dir }}.
780
826
 
781
827
  Using {{ tt.working_dir }} in the working_dir field creates a circular dependency.
782
828
 
783
829
  Args:
784
- text: The working_dir field value to validate
830
+ text: The working_dir field value to validate
785
831
 
786
832
  Raises:
787
- ExecutionError: If {{ tt.working_dir }} placeholder is found
833
+ ExecutionError: If {{ tt.working_dir }} placeholder is found
834
+ @athena: 5dc6ee41d403
788
835
  """
789
836
  import re
790
837
  # Pattern to match {{ tt.working_dir }} specifically
@@ -798,70 +845,86 @@ class Executor:
798
845
  )
799
846
 
800
847
  def _substitute_builtin(self, text: str, builtin_vars: dict[str, str]) -> str:
801
- """Substitute {{ tt.name }} placeholders in text.
848
+ """
849
+ Substitute {{ tt.name }} placeholders in text.
802
850
 
803
851
  Built-in variables are resolved at execution time.
804
852
 
805
853
  Args:
806
- text: Text with {{ tt.name }} placeholders
807
- builtin_vars: Built-in variable values
854
+ text: Text with {{ tt.name }} placeholders
855
+ builtin_vars: Built-in variable values
808
856
 
809
857
  Returns:
810
- Text with built-in variables substituted
858
+ Text with built-in variables substituted
811
859
 
812
860
  Raises:
813
- ValueError: If built-in variable is not defined
861
+ ValueError: If built-in variable is not defined
862
+ @athena: 463600a203f4
814
863
  """
815
864
  from tasktree.substitution import substitute_builtin_variables
816
865
  return substitute_builtin_variables(text, builtin_vars)
817
866
 
818
867
  def _substitute_args(self, cmd: str, args_dict: dict[str, Any], exported_args: set[str] | None = None) -> str:
819
- """Substitute {{ arg.name }} placeholders in command string.
868
+ """
869
+ Substitute {{ arg.name }} placeholders in command string.
820
870
 
821
871
  Variables are already substituted at parse time by the parser.
822
872
  This only handles runtime argument substitution.
823
873
 
824
874
  Args:
825
- cmd: Command with {{ arg.name }} placeholders
826
- args_dict: Argument values to substitute (only regular args)
827
- exported_args: Set of argument names that are exported (not available for substitution)
875
+ cmd: Command with {{ arg.name }} placeholders
876
+ args_dict: Argument values to substitute (only regular args)
877
+ exported_args: Set of argument names that are exported (not available for substitution)
828
878
 
829
879
  Returns:
830
- Command with arguments substituted
880
+ Command with arguments substituted
831
881
 
832
882
  Raises:
833
- ValueError: If an exported argument is used in template substitution
883
+ ValueError: If an exported argument is used in template substitution
884
+ @athena: 4261a91c6a98
834
885
  """
835
886
  from tasktree.substitution import substitute_arguments
836
887
  return substitute_arguments(cmd, args_dict, exported_args)
837
888
 
838
889
  def _substitute_env(self, text: str) -> str:
839
- """Substitute {{ env.NAME }} placeholders in text.
890
+ """
891
+ Substitute {{ env.NAME }} placeholders in text.
840
892
 
841
893
  Environment variables are resolved at execution time from os.environ.
842
894
 
843
895
  Args:
844
- text: Text with {{ env.NAME }} placeholders
896
+ text: Text with {{ env.NAME }} placeholders
845
897
 
846
898
  Returns:
847
- Text with environment variables substituted
899
+ Text with environment variables substituted
848
900
 
849
901
  Raises:
850
- ValueError: If environment variable is not set
902
+ ValueError: If environment variable is not set
903
+ @athena: 63becab531cd
851
904
  """
852
905
  from tasktree.substitution import substitute_environment
853
906
  return substitute_environment(text)
854
907
 
855
908
  def _get_all_inputs(self, task: Task) -> list[str]:
856
- """Get all inputs for a task (explicit + implicit from dependencies).
909
+ """
910
+ Get all inputs for a task (explicit + implicit from dependencies).
857
911
 
858
912
  Args:
859
- task: Task to get inputs for
913
+ task: Task to get inputs for
860
914
 
861
915
  Returns:
862
- List of input glob patterns
916
+ List of input glob patterns
917
+ @athena: ca7ed7a6682f
863
918
  """
864
- all_inputs = list(task.inputs)
919
+ # Extract paths from inputs (handle both anonymous strings and named dicts)
920
+ all_inputs = []
921
+ for inp in task.inputs:
922
+ if isinstance(inp, str):
923
+ all_inputs.append(inp)
924
+ elif isinstance(inp, dict):
925
+ # Named input - extract the path value(s)
926
+ all_inputs.extend(inp.values())
927
+
865
928
  implicit_inputs = get_implicit_inputs(self.recipe, task)
866
929
  all_inputs.extend(implicit_inputs)
867
930
  return all_inputs
@@ -869,18 +932,20 @@ class Executor:
869
932
  def _check_environment_changed(
870
933
  self, task: Task, cached_state: TaskState, env_name: str
871
934
  ) -> bool:
872
- """Check if environment definition has changed since last run.
935
+ """
936
+ Check if environment definition has changed since last run.
873
937
 
874
938
  For shell environments: checks YAML definition hash
875
939
  For Docker environments: checks YAML hash AND Docker image ID
876
940
 
877
941
  Args:
878
- task: Task to check
879
- cached_state: Cached state from previous run
880
- env_name: Effective environment name (from _get_effective_env_name)
942
+ task: Task to check
943
+ cached_state: Cached state from previous run
944
+ env_name: Effective environment name (from _get_effective_env_name)
881
945
 
882
946
  Returns:
883
- True if environment definition changed, False otherwise
947
+ True if environment definition changed, False otherwise
948
+ @athena: 052561b75455
884
949
  """
885
950
  # If using platform default (no environment), no definition to track
886
951
  if not env_name:
@@ -919,18 +984,20 @@ class Executor:
919
984
  def _check_docker_image_changed(
920
985
  self, env: Environment, cached_state: TaskState, env_name: str
921
986
  ) -> bool:
922
- """Check if Docker image ID has changed.
987
+ """
988
+ Check if Docker image ID has changed.
923
989
 
924
990
  Builds the image and compares the resulting image ID with the cached ID.
925
991
  This detects changes from unpinned base images, network-dependent builds, etc.
926
992
 
927
993
  Args:
928
- env: Docker environment definition
929
- cached_state: Cached state from previous run
930
- env_name: Environment name
994
+ env: Docker environment definition
995
+ cached_state: Cached state from previous run
996
+ env_name: Environment name
931
997
 
932
998
  Returns:
933
- True if image ID changed, False otherwise
999
+ True if image ID changed, False otherwise
1000
+ @athena: 8af77cb1be44
934
1001
  """
935
1002
  # Build/ensure image is built and get its ID
936
1003
  try:
@@ -953,7 +1020,8 @@ class Executor:
953
1020
  def _check_inputs_changed(
954
1021
  self, task: Task, cached_state: TaskState, all_inputs: list[str]
955
1022
  ) -> list[str]:
956
- """Check if any input files have changed since last run.
1023
+ """
1024
+ Check if any input files have changed since last run.
957
1025
 
958
1026
  Handles both regular file inputs and Docker-specific inputs:
959
1027
  - Regular files: checked via mtime
@@ -961,12 +1029,13 @@ class Executor:
961
1029
  - Dockerfile digests: checked via parsing and comparison
962
1030
 
963
1031
  Args:
964
- task: Task to check
965
- cached_state: Cached state from previous run
966
- all_inputs: All input glob patterns
1032
+ task: Task to check
1033
+ cached_state: Cached state from previous run
1034
+ all_inputs: All input glob patterns
967
1035
 
968
1036
  Returns:
969
- List of changed file paths
1037
+ List of changed file paths
1038
+ @athena: 15b13fd181bf
970
1039
  """
971
1040
  changed_files = []
972
1041
 
@@ -1047,13 +1116,15 @@ class Executor:
1047
1116
  return changed_files
1048
1117
 
1049
1118
  def _expand_output_paths(self, task: Task) -> list[str]:
1050
- """Extract all output paths from task outputs (both named and anonymous).
1119
+ """
1120
+ Extract all output paths from task outputs (both named and anonymous).
1051
1121
 
1052
1122
  Args:
1053
- task: Task with outputs to extract
1123
+ task: Task with outputs to extract
1054
1124
 
1055
1125
  Returns:
1056
- List of output path patterns (glob patterns as strings)
1126
+ List of output path patterns (glob patterns as strings)
1127
+ @athena: 848a28564b14
1057
1128
  """
1058
1129
  paths = []
1059
1130
  for output in task.outputs:
@@ -1066,13 +1137,15 @@ class Executor:
1066
1137
  return paths
1067
1138
 
1068
1139
  def _check_outputs_missing(self, task: Task) -> list[str]:
1069
- """Check if any declared outputs are missing.
1140
+ """
1141
+ Check if any declared outputs are missing.
1070
1142
 
1071
1143
  Args:
1072
- task: Task to check
1144
+ task: Task to check
1073
1145
 
1074
1146
  Returns:
1075
- List of output patterns that have no matching files
1147
+ List of output patterns that have no matching files
1148
+ @athena: 9ceac49b4e68
1076
1149
  """
1077
1150
  if not task.outputs:
1078
1151
  return []
@@ -1092,14 +1165,16 @@ class Executor:
1092
1165
  return missing_patterns
1093
1166
 
1094
1167
  def _expand_globs(self, patterns: list[str], working_dir: str) -> list[str]:
1095
- """Expand glob patterns to actual file paths.
1168
+ """
1169
+ Expand glob patterns to actual file paths.
1096
1170
 
1097
1171
  Args:
1098
- patterns: List of glob patterns
1099
- working_dir: Working directory to resolve patterns from
1172
+ patterns: List of glob patterns
1173
+ working_dir: Working directory to resolve patterns from
1100
1174
 
1101
1175
  Returns:
1102
- List of file paths (relative to working_dir)
1176
+ List of file paths (relative to working_dir)
1177
+ @athena: 5ba093866558
1103
1178
  """
1104
1179
  files = []
1105
1180
  base_path = self.recipe.project_root / working_dir
@@ -1116,11 +1191,13 @@ class Executor:
1116
1191
  return files
1117
1192
 
1118
1193
  def _update_state(self, task: Task, args_dict: dict[str, Any]) -> None:
1119
- """Update state after task execution.
1194
+ """
1195
+ Update state after task execution.
1120
1196
 
1121
1197
  Args:
1122
- task: Task that was executed
1123
- args_dict: Arguments used for execution
1198
+ task: Task that was executed
1199
+ args_dict: Arguments used for execution
1200
+ @athena: 1fcfdfcb9be9
1124
1201
  """
1125
1202
  # Compute hashes (include effective environment and dependencies)
1126
1203
  effective_env = self._get_effective_env_name(task)