tasktree 0.0.20__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
@@ -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 = {}
@@ -485,14 +516,16 @@ class Executor:
485
516
  return statuses
486
517
 
487
518
  def _run_task(self, task: Task, args_dict: dict[str, Any]) -> None:
488
- """Execute a single task.
519
+ """
520
+ Execute a single task.
489
521
 
490
522
  Args:
491
- task: Task to execute
492
- args_dict: Arguments to substitute in command
523
+ task: Task to execute
524
+ args_dict: Arguments to substitute in command
493
525
 
494
526
  Raises:
495
- ExecutionError: If task execution fails
527
+ ExecutionError: If task execution fails
528
+ @athena: 885c66658550
496
529
  """
497
530
  # Capture timestamp at task start for consistency (in UTC)
498
531
  task_start_time = datetime.now(timezone.utc)
@@ -566,18 +599,20 @@ class Executor:
566
599
  self, cmd: str, working_dir: Path, task_name: str, shell: str, shell_args: list[str],
567
600
  exported_env_vars: dict[str, str] | None = None
568
601
  ) -> None:
569
- """Execute a single-line command via shell.
602
+ """
603
+ Execute a single-line command via shell.
570
604
 
571
605
  Args:
572
- cmd: Command string
573
- working_dir: Working directory
574
- task_name: Task name (for error messages)
575
- shell: Shell executable to use
576
- shell_args: Arguments to pass to shell
577
- 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
578
612
 
579
613
  Raises:
580
- ExecutionError: If command execution fails
614
+ ExecutionError: If command execution fails
615
+ @athena: 46849e6a0bbb
581
616
  """
582
617
  # Prepare environment with exported args
583
618
  env = self._prepare_env_with_exports(exported_env_vars)
@@ -601,18 +636,20 @@ class Executor:
601
636
  self, cmd: str, working_dir: Path, task_name: str, shell: str, preamble: str,
602
637
  exported_env_vars: dict[str, str] | None = None
603
638
  ) -> None:
604
- """Execute a multi-line command via temporary script file.
639
+ """
640
+ Execute a multi-line command via temporary script file.
605
641
 
606
642
  Args:
607
- cmd: Multi-line command string
608
- working_dir: Working directory
609
- task_name: Task name (for error messages)
610
- shell: Shell to use for script execution
611
- preamble: Preamble text to prepend to script
612
- 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
613
649
 
614
650
  Raises:
615
- ExecutionError: If command execution fails
651
+ ExecutionError: If command execution fails
652
+ @athena: 825892b6db05
616
653
  """
617
654
  # Prepare environment with exported args
618
655
  env = self._prepare_env_with_exports(exported_env_vars)
@@ -671,17 +708,19 @@ class Executor:
671
708
  pass # Ignore cleanup errors
672
709
 
673
710
  def _substitute_builtin_in_environment(self, env: Environment, builtin_vars: dict[str, str]) -> Environment:
674
- """Substitute builtin and environment variables in environment fields.
711
+ """
712
+ Substitute builtin and environment variables in environment fields.
675
713
 
676
714
  Args:
677
- env: Environment to process
678
- builtin_vars: Built-in variable values
715
+ env: Environment to process
716
+ builtin_vars: Built-in variable values
679
717
 
680
718
  Returns:
681
- New Environment with builtin and environment variables substituted
719
+ New Environment with builtin and environment variables substituted
682
720
 
683
721
  Raises:
684
- ValueError: If builtin variable or environment variable is not defined
722
+ ValueError: If builtin variable or environment variable is not defined
723
+ @athena: 21e2ccd27dbb
685
724
  """
686
725
  from dataclasses import replace
687
726
 
@@ -728,17 +767,19 @@ class Executor:
728
767
  self, task: Task, env: Any, cmd: str, working_dir: Path,
729
768
  exported_env_vars: dict[str, str] | None = None
730
769
  ) -> None:
731
- """Execute task inside Docker container.
770
+ """
771
+ Execute task inside Docker container.
732
772
 
733
773
  Args:
734
- task: Task to execute
735
- env: Docker environment configuration
736
- cmd: Command to execute
737
- working_dir: Host working directory
738
- 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
739
779
 
740
780
  Raises:
741
- ExecutionError: If Docker execution fails
781
+ ExecutionError: If Docker execution fails
782
+ @athena: fe972e4c97a3
742
783
  """
743
784
  # Get builtin variables for substitution in environment fields
744
785
  task_start_time = datetime.now(timezone.utc)
@@ -780,15 +821,17 @@ class Executor:
780
821
  raise ExecutionError(str(e)) from e
781
822
 
782
823
  def _validate_no_working_dir_circular_ref(self, text: str) -> None:
783
- """Validate that working_dir field does not contain {{ tt.working_dir }}.
824
+ """
825
+ Validate that working_dir field does not contain {{ tt.working_dir }}.
784
826
 
785
827
  Using {{ tt.working_dir }} in the working_dir field creates a circular dependency.
786
828
 
787
829
  Args:
788
- text: The working_dir field value to validate
830
+ text: The working_dir field value to validate
789
831
 
790
832
  Raises:
791
- ExecutionError: If {{ tt.working_dir }} placeholder is found
833
+ ExecutionError: If {{ tt.working_dir }} placeholder is found
834
+ @athena: 5dc6ee41d403
792
835
  """
793
836
  import re
794
837
  # Pattern to match {{ tt.working_dir }} specifically
@@ -802,68 +845,76 @@ class Executor:
802
845
  )
803
846
 
804
847
  def _substitute_builtin(self, text: str, builtin_vars: dict[str, str]) -> str:
805
- """Substitute {{ tt.name }} placeholders in text.
848
+ """
849
+ Substitute {{ tt.name }} placeholders in text.
806
850
 
807
851
  Built-in variables are resolved at execution time.
808
852
 
809
853
  Args:
810
- text: Text with {{ tt.name }} placeholders
811
- builtin_vars: Built-in variable values
854
+ text: Text with {{ tt.name }} placeholders
855
+ builtin_vars: Built-in variable values
812
856
 
813
857
  Returns:
814
- Text with built-in variables substituted
858
+ Text with built-in variables substituted
815
859
 
816
860
  Raises:
817
- ValueError: If built-in variable is not defined
861
+ ValueError: If built-in variable is not defined
862
+ @athena: 463600a203f4
818
863
  """
819
864
  from tasktree.substitution import substitute_builtin_variables
820
865
  return substitute_builtin_variables(text, builtin_vars)
821
866
 
822
867
  def _substitute_args(self, cmd: str, args_dict: dict[str, Any], exported_args: set[str] | None = None) -> str:
823
- """Substitute {{ arg.name }} placeholders in command string.
868
+ """
869
+ Substitute {{ arg.name }} placeholders in command string.
824
870
 
825
871
  Variables are already substituted at parse time by the parser.
826
872
  This only handles runtime argument substitution.
827
873
 
828
874
  Args:
829
- cmd: Command with {{ arg.name }} placeholders
830
- args_dict: Argument values to substitute (only regular args)
831
- 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)
832
878
 
833
879
  Returns:
834
- Command with arguments substituted
880
+ Command with arguments substituted
835
881
 
836
882
  Raises:
837
- ValueError: If an exported argument is used in template substitution
883
+ ValueError: If an exported argument is used in template substitution
884
+ @athena: 4261a91c6a98
838
885
  """
839
886
  from tasktree.substitution import substitute_arguments
840
887
  return substitute_arguments(cmd, args_dict, exported_args)
841
888
 
842
889
  def _substitute_env(self, text: str) -> str:
843
- """Substitute {{ env.NAME }} placeholders in text.
890
+ """
891
+ Substitute {{ env.NAME }} placeholders in text.
844
892
 
845
893
  Environment variables are resolved at execution time from os.environ.
846
894
 
847
895
  Args:
848
- text: Text with {{ env.NAME }} placeholders
896
+ text: Text with {{ env.NAME }} placeholders
849
897
 
850
898
  Returns:
851
- Text with environment variables substituted
899
+ Text with environment variables substituted
852
900
 
853
901
  Raises:
854
- ValueError: If environment variable is not set
902
+ ValueError: If environment variable is not set
903
+ @athena: 63becab531cd
855
904
  """
856
905
  from tasktree.substitution import substitute_environment
857
906
  return substitute_environment(text)
858
907
 
859
908
  def _get_all_inputs(self, task: Task) -> list[str]:
860
- """Get all inputs for a task (explicit + implicit from dependencies).
909
+ """
910
+ Get all inputs for a task (explicit + implicit from dependencies).
861
911
 
862
912
  Args:
863
- task: Task to get inputs for
913
+ task: Task to get inputs for
864
914
 
865
915
  Returns:
866
- List of input glob patterns
916
+ List of input glob patterns
917
+ @athena: ca7ed7a6682f
867
918
  """
868
919
  # Extract paths from inputs (handle both anonymous strings and named dicts)
869
920
  all_inputs = []
@@ -881,18 +932,20 @@ class Executor:
881
932
  def _check_environment_changed(
882
933
  self, task: Task, cached_state: TaskState, env_name: str
883
934
  ) -> bool:
884
- """Check if environment definition has changed since last run.
935
+ """
936
+ Check if environment definition has changed since last run.
885
937
 
886
938
  For shell environments: checks YAML definition hash
887
939
  For Docker environments: checks YAML hash AND Docker image ID
888
940
 
889
941
  Args:
890
- task: Task to check
891
- cached_state: Cached state from previous run
892
- 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)
893
945
 
894
946
  Returns:
895
- True if environment definition changed, False otherwise
947
+ True if environment definition changed, False otherwise
948
+ @athena: 052561b75455
896
949
  """
897
950
  # If using platform default (no environment), no definition to track
898
951
  if not env_name:
@@ -931,18 +984,20 @@ class Executor:
931
984
  def _check_docker_image_changed(
932
985
  self, env: Environment, cached_state: TaskState, env_name: str
933
986
  ) -> bool:
934
- """Check if Docker image ID has changed.
987
+ """
988
+ Check if Docker image ID has changed.
935
989
 
936
990
  Builds the image and compares the resulting image ID with the cached ID.
937
991
  This detects changes from unpinned base images, network-dependent builds, etc.
938
992
 
939
993
  Args:
940
- env: Docker environment definition
941
- cached_state: Cached state from previous run
942
- env_name: Environment name
994
+ env: Docker environment definition
995
+ cached_state: Cached state from previous run
996
+ env_name: Environment name
943
997
 
944
998
  Returns:
945
- True if image ID changed, False otherwise
999
+ True if image ID changed, False otherwise
1000
+ @athena: 8af77cb1be44
946
1001
  """
947
1002
  # Build/ensure image is built and get its ID
948
1003
  try:
@@ -965,7 +1020,8 @@ class Executor:
965
1020
  def _check_inputs_changed(
966
1021
  self, task: Task, cached_state: TaskState, all_inputs: list[str]
967
1022
  ) -> list[str]:
968
- """Check if any input files have changed since last run.
1023
+ """
1024
+ Check if any input files have changed since last run.
969
1025
 
970
1026
  Handles both regular file inputs and Docker-specific inputs:
971
1027
  - Regular files: checked via mtime
@@ -973,12 +1029,13 @@ class Executor:
973
1029
  - Dockerfile digests: checked via parsing and comparison
974
1030
 
975
1031
  Args:
976
- task: Task to check
977
- cached_state: Cached state from previous run
978
- 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
979
1035
 
980
1036
  Returns:
981
- List of changed file paths
1037
+ List of changed file paths
1038
+ @athena: 15b13fd181bf
982
1039
  """
983
1040
  changed_files = []
984
1041
 
@@ -1059,13 +1116,15 @@ class Executor:
1059
1116
  return changed_files
1060
1117
 
1061
1118
  def _expand_output_paths(self, task: Task) -> list[str]:
1062
- """Extract all output paths from task outputs (both named and anonymous).
1119
+ """
1120
+ Extract all output paths from task outputs (both named and anonymous).
1063
1121
 
1064
1122
  Args:
1065
- task: Task with outputs to extract
1123
+ task: Task with outputs to extract
1066
1124
 
1067
1125
  Returns:
1068
- List of output path patterns (glob patterns as strings)
1126
+ List of output path patterns (glob patterns as strings)
1127
+ @athena: 848a28564b14
1069
1128
  """
1070
1129
  paths = []
1071
1130
  for output in task.outputs:
@@ -1078,13 +1137,15 @@ class Executor:
1078
1137
  return paths
1079
1138
 
1080
1139
  def _check_outputs_missing(self, task: Task) -> list[str]:
1081
- """Check if any declared outputs are missing.
1140
+ """
1141
+ Check if any declared outputs are missing.
1082
1142
 
1083
1143
  Args:
1084
- task: Task to check
1144
+ task: Task to check
1085
1145
 
1086
1146
  Returns:
1087
- List of output patterns that have no matching files
1147
+ List of output patterns that have no matching files
1148
+ @athena: 9ceac49b4e68
1088
1149
  """
1089
1150
  if not task.outputs:
1090
1151
  return []
@@ -1104,14 +1165,16 @@ class Executor:
1104
1165
  return missing_patterns
1105
1166
 
1106
1167
  def _expand_globs(self, patterns: list[str], working_dir: str) -> list[str]:
1107
- """Expand glob patterns to actual file paths.
1168
+ """
1169
+ Expand glob patterns to actual file paths.
1108
1170
 
1109
1171
  Args:
1110
- patterns: List of glob patterns
1111
- working_dir: Working directory to resolve patterns from
1172
+ patterns: List of glob patterns
1173
+ working_dir: Working directory to resolve patterns from
1112
1174
 
1113
1175
  Returns:
1114
- List of file paths (relative to working_dir)
1176
+ List of file paths (relative to working_dir)
1177
+ @athena: 5ba093866558
1115
1178
  """
1116
1179
  files = []
1117
1180
  base_path = self.recipe.project_root / working_dir
@@ -1128,11 +1191,13 @@ class Executor:
1128
1191
  return files
1129
1192
 
1130
1193
  def _update_state(self, task: Task, args_dict: dict[str, Any]) -> None:
1131
- """Update state after task execution.
1194
+ """
1195
+ Update state after task execution.
1132
1196
 
1133
1197
  Args:
1134
- task: Task that was executed
1135
- args_dict: Arguments used for execution
1198
+ task: Task that was executed
1199
+ args_dict: Arguments used for execution
1200
+ @athena: 1fcfdfcb9be9
1136
1201
  """
1137
1202
  # Compute hashes (include effective environment and dependencies)
1138
1203
  effective_env = self._get_effective_env_name(task)