tasktree 0.0.21__py3-none-any.whl → 0.0.22__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/graph.py CHANGED
@@ -4,7 +4,6 @@ Dependency resolution using topological sorting.
4
4
  """
5
5
 
6
6
  from graphlib import TopologicalSorter
7
- from pathlib import Path
8
7
  from typing import Any
9
8
 
10
9
  from tasktree.hasher import hash_args
@@ -13,7 +12,6 @@ from tasktree.parser import (
13
12
  Task,
14
13
  DependencyInvocation,
15
14
  parse_dependency_spec,
16
- parse_arg_spec,
17
15
  )
18
16
  from tasktree.substitution import substitute_dependency_args
19
17
 
@@ -58,7 +56,7 @@ def resolve_dependency_invocation(
58
56
  parent_task_name: str,
59
57
  parent_args: dict[str, Any],
60
58
  parent_exported_args: set[str],
61
- recipe: Recipe
59
+ recipe: Recipe,
62
60
  ) -> DependencyInvocation:
63
61
  """
64
62
  Parse dependency specification and substitute parent argument templates.
@@ -246,9 +244,7 @@ class TaskNode:
246
244
 
247
245
 
248
246
  def resolve_execution_order(
249
- recipe: Recipe,
250
- target_task: str,
251
- target_args: dict[str, Any] | None = None
247
+ recipe: Recipe, target_task: str, target_args: dict[str, Any] | None = None
252
248
  ) -> list[tuple[str, dict[str, Any]]]:
253
249
  """
254
250
  Resolve execution order for a task and its dependencies.
@@ -273,7 +269,9 @@ def resolve_execution_order(
273
269
  graph: dict[TaskNode, set[TaskNode]] = {}
274
270
 
275
271
  # Track seen nodes to detect duplicates
276
- seen_invocations: dict[tuple[str, str], TaskNode] = {} # (task_name, args_hash) -> node
272
+ seen_invocations: dict[
273
+ tuple[str, str], TaskNode
274
+ ] = {} # (task_name, args_hash) -> node
277
275
 
278
276
  def get_or_create_node(task_name: str, args: dict[str, Any] | None) -> TaskNode:
279
277
  """Get existing node or create new one for this invocation."""
@@ -307,7 +305,7 @@ def resolve_execution_order(
307
305
  parent_task_name=node.task_name,
308
306
  parent_args=node.args or {},
309
307
  parent_exported_args=parent_exported_args,
310
- recipe=recipe
308
+ recipe=recipe,
311
309
  )
312
310
 
313
311
  # Create or get node for this dependency invocation
@@ -592,7 +590,9 @@ def get_implicit_inputs(recipe: Recipe, task: Task) -> list[str]:
592
590
  return implicit_inputs
593
591
 
594
592
 
595
- def build_dependency_tree(recipe: Recipe, target_task: str, target_args: dict[str, Any] | None = None) -> dict:
593
+ def build_dependency_tree(
594
+ recipe: Recipe, target_task: str, target_args: dict[str, Any] | None = None
595
+ ) -> dict:
596
596
  """
597
597
  Build a tree structure representing dependencies for visualization.
598
598
 
@@ -622,12 +622,17 @@ def build_dependency_tree(recipe: Recipe, target_task: str, target_args: dict[st
622
622
 
623
623
  # Create node identifier for cycle detection
624
624
  from tasktree.hasher import hash_args
625
+
625
626
  args_dict = args or {}
626
627
  node_id = (task_name, hash_args(args_dict) if args_dict else "")
627
628
 
628
629
  # Detect cycles in current recursion path
629
630
  if node_id in current_path:
630
- display_name = task_name if not args_dict else f"{task_name}({', '.join(f'{k}={v}' for k, v in sorted(args_dict.items()))})"
631
+ display_name = (
632
+ task_name
633
+ if not args_dict
634
+ else f"{task_name}({', '.join(f'{k}={v}' for k, v in sorted(args_dict.items()))})"
635
+ )
631
636
  return {"name": display_name, "deps": [], "cycle": True}
632
637
 
633
638
  current_path.add(node_id)
tasktree/hasher.py CHANGED
@@ -28,7 +28,9 @@ def _arg_sort_key(arg: str | dict[str, Any]) -> str:
28
28
  return arg
29
29
 
30
30
 
31
- def _normalize_choices_lists(args: list[str | dict[str, Any]]) -> list[str | dict[str, Any]]:
31
+ def _normalize_choices_lists(
32
+ args: list[str | dict[str, Any]],
33
+ ) -> list[str | dict[str, Any]]:
32
34
  """
33
35
  Normalize argument choices lists by sorting them for deterministic hashing.
34
36
 
@@ -46,8 +48,11 @@ def _normalize_choices_lists(args: list[str | dict[str, Any]]) -> list[str | di
46
48
  # Deep copy and sort choices if present
47
49
  normalized = {}
48
50
  for key, value in arg.items():
49
- if isinstance(value, dict) and 'choices' in value:
50
- normalized[key] = {**value, 'choices': sorted(value['choices'], key=str)}
51
+ if isinstance(value, dict) and "choices" in value:
52
+ normalized[key] = {
53
+ **value,
54
+ "choices": sorted(value["choices"], key=str),
55
+ }
51
56
  else:
52
57
  normalized[key] = value
53
58
  normalized_args.append(normalized)
@@ -94,7 +99,7 @@ def hash_task(
94
99
  working_dir: str,
95
100
  args: list[str | dict[str, Any]],
96
101
  env: str = "",
97
- deps: list[str | dict[str, Any]] | None = None
102
+ deps: list[str | dict[str, Any]] | None = None,
98
103
  ) -> str:
99
104
  """
100
105
  Hash task definition including dependencies.
@@ -134,7 +139,10 @@ def hash_task(
134
139
  else:
135
140
  normalized_deps.append(dep)
136
141
  # Sort using JSON serialization for consistent ordering
137
- data["deps"] = sorted(normalized_deps, key=lambda x: json.dumps(x, sort_keys=True) if isinstance(x, dict) else x)
142
+ data["deps"] = sorted(
143
+ normalized_deps,
144
+ key=lambda x: json.dumps(x, sort_keys=True) if isinstance(x, dict) else x,
145
+ )
138
146
 
139
147
  serialized = json.dumps(data, sort_keys=True, separators=(",", ":"))
140
148
  return hashlib.sha256(serialized.encode()).hexdigest()[:8]
@@ -168,7 +176,6 @@ def hash_environment_definition(env) -> str:
168
176
  @athena: 2de34f1a0b4a
169
177
  """
170
178
  # Import inside function to avoid circular dependency
171
- from tasktree.parser import Environment
172
179
 
173
180
  # Handle args - can be list (shell args) or dict (docker build args)
174
181
  args_value = env.args